|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Широкополосный FM и ELF Demodulator</title> |
|
<style> |
|
body { font-family: Arial, sans-serif; } |
|
#controls { margin-bottom: 20px; } |
|
</style> |
|
</head> |
|
<body> |
|
<div id="controls"> |
|
<label for="frequencySlider">Частота (Hz): </label> |
|
<input type="range" id="frequencySlider" min="3" max="20000" step="1" value="1000"> |
|
<span id="frequencyValue">1000</span> Hz |
|
<button id="toggleAudio">Включить звук</button> |
|
<button id="toggleELF">Включить ELF режим</button> |
|
</div> |
|
<canvas id="elfCanvas" width="800" height="150" style="border:1px solid #000000;"></canvas> |
|
<script> |
|
let audioContext; |
|
let analyser; |
|
let microphone; |
|
let canvas; |
|
let canvasCtx; |
|
let scriptProcessor; |
|
let prevPhase = 0; |
|
let demodulatedSignal = []; |
|
let gainNode; |
|
let isAudioEnabled = false; |
|
let isELFMode = false; |
|
let centerFrequency = 1000; |
|
let elfBuffer = []; |
|
let elfCanvas; |
|
let elfCtx; |
|
|
|
function startAudioAnalysis() { |
|
audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
|
analyser = audioContext.createAnalyser(); |
|
analyser.fftSize = 2048; |
|
|
|
scriptProcessor = audioContext.createScriptProcessor(1024, 1, 1); |
|
gainNode = audioContext.createGain(); |
|
gainNode.gain.setValueAtTime(0, audioContext.currentTime); |
|
|
|
navigator.mediaDevices.getUserMedia({ audio: true, video: false }) |
|
.then(function(stream) { |
|
microphone = audioContext.createMediaStreamSource(stream); |
|
microphone.connect(analyser); |
|
analyser.connect(scriptProcessor); |
|
scriptProcessor.connect(gainNode); |
|
gainNode.connect(audioContext.destination); |
|
|
|
canvas = document.createElement('canvas'); |
|
canvas.width = 800; |
|
canvas.height = 300; |
|
document.body.appendChild(canvas); |
|
canvasCtx = canvas.getContext('2d'); |
|
|
|
elfCanvas = document.getElementById('elfCanvas'); |
|
elfCtx = elfCanvas.getContext('2d'); |
|
|
|
drawSpectrum(); |
|
}) |
|
.catch(function(err) { |
|
console.error('Ошибка доступа к микрофону:', err); |
|
}); |
|
|
|
scriptProcessor.onaudioprocess = processAudio; |
|
} |
|
|
|
function drawSpectrum() { |
|
requestAnimationFrame(drawSpectrum); |
|
|
|
const bufferLength = analyser.frequencyBinCount; |
|
const dataArray = new Uint8Array(bufferLength); |
|
analyser.getByteFrequencyData(dataArray); |
|
|
|
canvasCtx.fillStyle = 'rgb(0, 0, 0)'; |
|
canvasCtx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
const barWidth = (canvas.width / bufferLength) * 2.5; |
|
let x = 0; |
|
|
|
for(let i = 0; i < bufferLength; i++) { |
|
const barHeight = dataArray[i] / 2; |
|
|
|
canvasCtx.fillStyle = `rgb(${barHeight + 100}, 50, 50)`; |
|
canvasCtx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); |
|
|
|
x += barWidth + 1; |
|
} |
|
|
|
|
|
canvasCtx.strokeStyle = 'rgb(0, 255, 0)'; |
|
canvasCtx.beginPath(); |
|
for(let i = 0; i < demodulatedSignal.length; i++) { |
|
const x = (i / demodulatedSignal.length) * canvas.width; |
|
const y = (0.5 - demodulatedSignal[i] / 2) * canvas.height; |
|
if(i === 0) { |
|
canvasCtx.moveTo(x, y); |
|
} else { |
|
canvasCtx.lineTo(x, y); |
|
} |
|
} |
|
canvasCtx.stroke(); |
|
|
|
|
|
const centerX = (centerFrequency / 20000) * canvas.width; |
|
canvasCtx.strokeStyle = 'rgb(255, 255, 0)'; |
|
canvasCtx.beginPath(); |
|
canvasCtx.moveTo(centerX, 0); |
|
canvasCtx.lineTo(centerX, canvas.height); |
|
canvasCtx.stroke(); |
|
|
|
|
|
if (isELFMode) { |
|
elfCtx.fillStyle = 'rgb(0, 0, 0)'; |
|
elfCtx.fillRect(0, 0, elfCanvas.width, elfCanvas.height); |
|
elfCtx.strokeStyle = 'rgb(0, 255, 0)'; |
|
elfCtx.beginPath(); |
|
for(let i = 0; i < elfBuffer.length; i++) { |
|
const x = (i / elfBuffer.length) * elfCanvas.width; |
|
const y = (0.5 - elfBuffer[i] / 2) * elfCanvas.height; |
|
if(i === 0) { |
|
elfCtx.moveTo(x, y); |
|
} else { |
|
elfCtx.lineTo(x, y); |
|
} |
|
} |
|
elfCtx.stroke(); |
|
} |
|
} |
|
|
|
function processAudio(audioProcessingEvent) { |
|
const inputBuffer = audioProcessingEvent.inputBuffer; |
|
const outputBuffer = audioProcessingEvent.outputBuffer; |
|
const inputData = inputBuffer.getChannelData(0); |
|
const outputData = outputBuffer.getChannelData(0); |
|
|
|
demodulatedSignal = []; |
|
|
|
for(let i = 0; i < inputData.length; i++) { |
|
const phase = Math.atan2(inputData[i], prevPhase); |
|
const instantFreq = (phase - prevPhase + Math.PI) % (2 * Math.PI) - Math.PI; |
|
let demodulated; |
|
if (isELFMode) { |
|
demodulated = lowPassFilter(instantFreq); |
|
} else { |
|
demodulated = instantFreq * (centerFrequency / 1000); |
|
} |
|
demodulatedSignal.push(demodulated); |
|
outputData[i] = demodulated; |
|
prevPhase = inputData[i]; |
|
} |
|
|
|
if (isELFMode) { |
|
elfBuffer = elfBuffer.concat(demodulatedSignal); |
|
if (elfBuffer.length > 1000) { |
|
elfBuffer = elfBuffer.slice(-1000); |
|
} |
|
} |
|
} |
|
|
|
function lowPassFilter(input) { |
|
|
|
const alpha = 0.1; |
|
this.filtered = (this.filtered || 0) * (1 - alpha) + input * alpha; |
|
return this.filtered; |
|
} |
|
|
|
function toggleAudio() { |
|
if (isAudioEnabled) { |
|
gainNode.gain.setValueAtTime(0, audioContext.currentTime); |
|
document.getElementById('toggleAudio').textContent = 'Включить звук'; |
|
} else { |
|
gainNode.gain.setValueAtTime(1, audioContext.currentTime); |
|
document.getElementById('toggleAudio').textContent = 'Выключить звук'; |
|
} |
|
isAudioEnabled = !isAudioEnabled; |
|
} |
|
|
|
function toggleELF() { |
|
isELFMode = !isELFMode; |
|
if (isELFMode) { |
|
document.getElementById('toggleELF').textContent = 'Выключить ELF режим'; |
|
document.getElementById('frequencySlider').min = 3; |
|
document.getElementById('frequencySlider').max = 30; |
|
centerFrequency = 15; |
|
document.getElementById('frequencySlider').value = centerFrequency; |
|
updateFrequency(); |
|
} else { |
|
document.getElementById('toggleELF').textContent = 'Включить ELF режим'; |
|
document.getElementById('frequencySlider').min = 3; |
|
document.getElementById('frequencySlider').max = 20000; |
|
centerFrequency = 1000; |
|
document.getElementById('frequencySlider').value = centerFrequency; |
|
updateFrequency(); |
|
} |
|
elfBuffer = []; |
|
} |
|
|
|
function updateFrequency() { |
|
centerFrequency = parseFloat(document.getElementById('frequencySlider').value); |
|
document.getElementById('frequencyValue').textContent = centerFrequency.toFixed(0); |
|
} |
|
|
|
window.onload = function() { |
|
startAudioAnalysis(); |
|
document.getElementById('toggleAudio').addEventListener('click', toggleAudio); |
|
document.getElementById('toggleELF').addEventListener('click', toggleELF); |
|
document.getElementById('frequencySlider').addEventListener('input', updateFrequency); |
|
}; |
|
</script> |
|
</body> |
|
</html> |