159 lines
5.0 KiB
HTML
159 lines
5.0 KiB
HTML
<div class="container" style="max-width:860px;">
|
||
<div class="mb-2">
|
||
<div class="small text-secondary fw-semibold mb-1">Modulierendes Signal (Nachricht)</div>
|
||
<canvas id="fmpm-c0" height="140" class="w-100 d-block rounded"></canvas>
|
||
</div>
|
||
<div class="mb-2">
|
||
<div class="small text-secondary fw-semibold mb-1">Trägersignal (unmoduliert)</div>
|
||
<canvas id="fmpm-c1" height="70" class="w-100 d-block rounded"></canvas>
|
||
</div>
|
||
<div class="mb-2">
|
||
<div class="small text-secondary fw-semibold mb-1">FM – Frequenz des Trägers folgt dem Signal-Wert</div>
|
||
<canvas id="fmpm-c2" height="70" class="w-100 d-block rounded"></canvas>
|
||
</div>
|
||
<div class="mb-3">
|
||
<div class="small text-secondary fw-semibold mb-1">PM – Phase des Trägers folgt dem Signal-Wert direkt</div>
|
||
<canvas id="fmpm-c3" height="70" class="w-100 d-block rounded"></canvas>
|
||
</div>
|
||
<hr>
|
||
<div class="d-flex flex-column gap-3">
|
||
<div class="row align-items-center g-2">
|
||
<div class="col-auto" style="min-width:170px;">
|
||
<label class="col-form-label col-form-label-sm">Signalform</label>
|
||
</div>
|
||
<div class="col">
|
||
<select class="form-select form-select-sm" id="fmpm-shape">
|
||
<option value="sine">Sinus</option>
|
||
<option value="square">Rechteck</option>
|
||
<option value="saw">Sägezahn</option>
|
||
<option value="ramp">Linearer Anstieg</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
const IDS = ['fmpm-c0','fmpm-c1','fmpm-c2','fmpm-c3'];
|
||
const COLORS = ['#1D9E75','#888780','#378ADD','#D85A30'];
|
||
const N = 1200;
|
||
const FC = 8;
|
||
const DEPTH = 1.5;
|
||
|
||
function signal(shape, n) {
|
||
const t = n / N, fs = 1.2;
|
||
switch (shape) {
|
||
case 'sine': return Math.sin(2 * Math.PI * fs * t);
|
||
case 'square': return Math.sign(Math.sin(2 * Math.PI * fs * t));
|
||
case 'saw': return 2 * (fs * t % 1) - 1;
|
||
case 'ramp': return Math.max(-1, Math.min(1, 2 * t - 1));
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
function draw() {
|
||
const depth = DEPTH;
|
||
const shape = document.getElementById('fmpm-shape').value;
|
||
const gridCount = 24;
|
||
|
||
const cvs = IDS.map(id => document.getElementById(id));
|
||
const dpr = window.devicePixelRatio || 1;
|
||
const w = cvs[0].offsetWidth || 800;
|
||
|
||
cvs.forEach(c => {
|
||
c.width = w * dpr;
|
||
c.height = c.offsetHeight * dpr;
|
||
});
|
||
|
||
let fmPhase = 0;
|
||
const pts = new Array(N);
|
||
for (let n = 0; n < N; n++) {
|
||
const t = n / N;
|
||
const sig = signal(shape, n);
|
||
const car = Math.sin(2 * Math.PI * FC * t);
|
||
fmPhase += 2 * Math.PI * (FC + depth * sig) / N;
|
||
const fm = Math.sin(fmPhase);
|
||
const pm = Math.sin(2 * Math.PI * FC * t + depth * sig);
|
||
pts[n] = { sig, car, fm, pm };
|
||
}
|
||
|
||
const zeros = [];
|
||
for (let n = 1; n < N; n++) {
|
||
if (pts[n-1].car <= 0 && pts[n].car > 0) zeros.push(n / N);
|
||
}
|
||
|
||
const series = [
|
||
pts.map(p => p.sig),
|
||
pts.map(p => p.car),
|
||
pts.map(p => p.fm),
|
||
pts.map(p => p.pm),
|
||
];
|
||
|
||
const gH = 'rgba(0,0,0,1)';
|
||
const gV = 'rgba(70,70,70,.5)';
|
||
const zV = 'rgba(120,120,120,0)';
|
||
|
||
series.forEach((data, i) => {
|
||
const c = cvs[i];
|
||
const ctx = c.getContext('2d');
|
||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||
ctx.scale(dpr, dpr);
|
||
const h = c.offsetHeight;
|
||
const mid = h / 2;
|
||
const amp = mid * 0.78;
|
||
|
||
ctx.strokeStyle = gH;
|
||
ctx.lineWidth = 0.9;
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath(); ctx.moveTo(0, mid); ctx.lineTo(w, mid); ctx.stroke();
|
||
|
||
ctx.strokeStyle = gV;
|
||
ctx.lineWidth = 0.9;
|
||
ctx.setLineDash([4, 4]);
|
||
for (let g = 1; g < gridCount; g++) {
|
||
const x = (g / gridCount) * w;
|
||
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
|
||
}
|
||
|
||
if (i === 2 || i === 3) {
|
||
ctx.strokeStyle = zV;
|
||
ctx.lineWidth = 0.5;
|
||
ctx.setLineDash([2, 5]);
|
||
zeros.forEach(t => {
|
||
const x = t * w;
|
||
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
|
||
});
|
||
}
|
||
|
||
if (i === 1) {
|
||
ctx.setLineDash([]);
|
||
ctx.fillStyle = 'rgba(95,95,95,.60)';
|
||
zeros.forEach(t => {
|
||
ctx.beginPath();
|
||
ctx.arc(t * w, mid, 2.5, 0, 2 * Math.PI);
|
||
ctx.fill();
|
||
});
|
||
}
|
||
|
||
ctx.setLineDash([]);
|
||
ctx.strokeStyle = COLORS[i];
|
||
ctx.lineWidth = i === 0 ? 2 : 1.5;
|
||
ctx.lineJoin = 'round';
|
||
ctx.beginPath();
|
||
data.forEach((v, n) => {
|
||
const x = n / N * w;
|
||
const y = mid - v * amp;
|
||
n === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
||
});
|
||
ctx.stroke();
|
||
});
|
||
}
|
||
|
||
document.getElementById('fmpm-shape').addEventListener('change', draw);
|
||
|
||
new ResizeObserver(draw).observe(document.getElementById('fmpm-c0'));
|
||
draw();
|
||
})();
|
||
</script>
|