Merge pull request #161 from DARC-e-V/feature/pm-applet

Feature/pm applet
This commit is contained in:
Matthias Jung
2026-03-29 23:24:58 +02:00
committed by GitHub
2 changed files with 192 additions and 6 deletions
+158
View File
@@ -0,0 +1,158 @@
<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>