Merge pull request #161 from DARC-e-V/feature/pm-applet
Feature/pm applet
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user