Indicador Mark / Space HTML
Segue uma abordagem visual do Tuning Scope de tubo (conhecido como Cross-line Indicator)!
Estão sintonizados 2125Hz e 2295Hz
Site com áudio RTTY : http://internet-tty.net:8000/ITTY
Áudio no youtube: RTTY Text-to-Audio File Converter
Segue o código!
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>RTTY Tuning Scope - CRT Style</title>
<style>
body { background: #1a1a1a; color: #00ff00; font-family: 'Courier New', monospace; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; }
.container { border: 4px solid #333; border-radius: 50%; padding: 20px; background: #000; box-shadow: 0 0 50px #004400; position: relative; }
canvas { background: #001100; border-radius: 50%; border: 2px solid #003300; width: 400px; height: 400px; }
.controls { margin-top: 20px; text-align: center; display: flex; flex-direction: column; gap: 10px; align-items: center; }
button { background: #004400; color: #00ff00; border: 1px solid #00ff00; padding: 10px 20px; cursor: pointer; font-size: 16px; transition: 0.3s; font-weight: bold; }
button:hover { background: #00ff00; color: #000; }
.info { font-size: 12px; color: #008800; }
input[type=range] { width: 250px; accent-color: #00ff00; cursor: pointer; }
label { font-size: 14px; }
</style>
</head>
<body>
<h2>RTTY X-Y SCOPE</h2>
<div class="container">
<canvas id="scope" width="400" height="400"></canvas>
</div>
<div class="controls">
<button id="startBtn">ATIVAR SENSOR/ÁUDIO</button>
<div style="display: flex; align-items: center; gap: 10px;">
<label>SENSIVEL.</label>
<input type="range" id="gainSlider" min="1" max="50" step="1" value="15">
</div>
<div class="info">Sintonize em: Mark 2125Hz (V) | Space 2295Hz (H)</div>
</div>
<script>
let audioCtx, analyserMark, analyserSpace, source, gainNode;
const canvas = document.getElementById('scope');
const ctx = canvas.getContext('2d');
const gainSlider = document.getElementById('gainSlider');
async function initAudio() {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
try {
// DESATIVAR AGC e Noise Suppression para o sinal não sumir
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
}
});
source = audioCtx.createMediaStreamSource(stream);
// Criar o amplificador (Gain Node)
gainNode = audioCtx.createGain();
gainNode.gain.value = gainSlider.value;
// Filtro para Mark (2125 Hz)
const filterMark = audioCtx.createBiquadFilter();
filterMark.type = "bandpass";
filterMark.frequency.value = 2125;
filterMark.Q.value = 25;
// Filtro para Space (2295 Hz)
const filterSpace = audioCtx.createBiquadFilter();
filterSpace.type = "bandpass";
filterSpace.frequency.value = 2295;
filterSpace.Q.value = 25;
analyserMark = audioCtx.createAnalyser();
analyserSpace = audioCtx.createAnalyser();
analyserMark.fftSize = 1024;
analyserSpace.fftSize = 1024;
// Fluxo: Microfone -> Ganho -> Filtros -> Analisadores
source.connect(gainNode);
gainNode.connect(filterMark);
filterMark.connect(analyserMark);
gainNode.connect(filterSpace);
filterSpace.connect(analyserSpace);
draw();
document.getElementById('startBtn').innerText = "INDICADOR ATIVO";
} catch (err) {
alert("Erro ao acessar microfone: " + err);
}
}
function draw() {
requestAnimationFrame(draw);
const dataMark = new Uint8Array(analyserMark.frequencyBinCount);
const dataSpace = new Uint8Array(analyserSpace.frequencyBinCount);
analyserMark.getByteTimeDomainData(dataMark);
analyserSpace.getByteTimeDomainData(dataSpace);
// Limpeza rápida para manter o traço nítido
ctx.fillStyle = "rgba(0, 10, 0, 0.4)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Desenhar a grade (retículo)
ctx.strokeStyle = "rgba(0, 50, 0, 0.5)";
ctx.beginPath();
ctx.moveTo(200, 0); ctx.lineTo(200, 400);
ctx.moveTo(0, 200); ctx.lineTo(400, 200);
ctx.stroke();
// Desenhar o traço X-Y
ctx.beginPath();
ctx.strokeStyle = "#44ff88";
ctx.lineWidth = 2.5;
ctx.shadowBlur = 10;
ctx.shadowColor = "#44ff88";
// Multiplicador visual para expandir o desenho
const zoom = 1.4;
for (let i = 0; i < dataMark.length; i += 2) {
// Normaliza de 0-255 para -1 a 1, e escala para o tamanho do canvas (200px de raio)
let yNorm = (dataMark[i] - 128) / 128;
let xNorm = (dataSpace[i] - 128) / 128;
let y = 200 + (yNorm * 200 * zoom);
let x = 200 + (xNorm * 200 * zoom);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
// Atualiza o ganho quando você move o slider
gainSlider.oninput = () => {
if (gainNode) gainNode.gain.value = gainSlider.value;
};
document.getElementById('startBtn').addEventListener('click', () => {
if (!audioCtx) initAudio();
else if (audioCtx.state === 'suspended') audioCtx.resume();
});
</script>
</body>
</html>

Comentários
Postar um comentário