- Sections and slides rewritten in Swedish; German law and institutions replaced by Swedish/CEPT/ITU equivalents (PTS, SSA, PTSFS 2025:1, SSMFS 2008:18), unverified statements marked "% TODO VERIFY" - New toc: INSTEG, HAREC, UPPGRADERING; section idents are Swedish (see ident-map.tsv) - Exam-question tags, BNetzA question pool, solutions and the German SWL course removed - Static pages, snippets, applets, READMEs and image alt-texts translated - TikZ labels translated; SVG drawings regenerated (drawing 689 still fails to build upstream: it references a missing photo) - Changed from the original: not endorsed by DARC e.V., SSA or PTS Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
168 lines
4.1 KiB
HTML
168 lines
4.1 KiB
HTML
<b>Brum på 50 Hz / 100 Hz</b>
|
|
<p>
|
|
Här kan du lyssna på brumtoner med frekvenserna $\qty{50}{\hertz}$ och $\qty{100}{\hertz}$.
|
|
</p>
|
|
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<button type="button" class="btn btn-primary" onclick="playRippleTone(50)">
|
|
Spela 50 Hz
|
|
</button>
|
|
|
|
<button type="button" class="btn btn-primary" onclick="playRippleTone(100)">
|
|
Spela 100 Hz
|
|
</button>
|
|
|
|
<button type="button" class="btn btn-secondary" onclick="stopTone()">
|
|
Stopp
|
|
</button>
|
|
</div>
|
|
|
|
<p id="toneStatus" class="mt-3 mb-0 text-muted">
|
|
Ingen ton aktiv.
|
|
</p>
|
|
|
|
<script>
|
|
let audioContext = null;
|
|
let sourceNode = null;
|
|
let gainNode = null;
|
|
|
|
function ensureAudioContext() {
|
|
if (!audioContext) {
|
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
}
|
|
|
|
if (audioContext.state === "suspended") {
|
|
audioContext.resume();
|
|
}
|
|
}
|
|
|
|
function smoothstep(x) {
|
|
x = Math.max(0, Math.min(1, x));
|
|
return x * x * (3 - 2 * x);
|
|
}
|
|
|
|
function rippleShape(phase) {
|
|
/*
|
|
phase: 0 ... 1
|
|
|
|
Modell:
|
|
- start nere
|
|
- relativt snabb, mjuk stigning
|
|
- avrundad topp
|
|
- långsammare, nästan linjärt fall
|
|
|
|
Detta är ingen exakt simulering av ett nätaggregat, utan en väl hörbar
|
|
approximation av det rippel som visas i bilden.
|
|
*/
|
|
|
|
const chargePart = 0.36; // andel av perioden för uppladdningen
|
|
|
|
if (phase < chargePart) {
|
|
const x = phase / chargePart;
|
|
|
|
// snabb stigning med mjuk avrundning
|
|
return -1 + 2 * smoothstep(x);
|
|
} else {
|
|
const x = (phase - chargePart) / (1 - chargePart);
|
|
|
|
// långsamt fall, lätt utjämnat
|
|
return 1 - 2 * x;
|
|
}
|
|
}
|
|
|
|
function createRippleBuffer(frequency) {
|
|
const sampleRate = audioContext.sampleRate;
|
|
|
|
/*
|
|
1 sekunds buffert:
|
|
Vid 50 Hz innehåller den 50 perioder,
|
|
vid 100 Hz 100 perioder.
|
|
Därmed loopar den rent.
|
|
*/
|
|
const duration = 1.0;
|
|
const length = Math.round(sampleRate * duration);
|
|
const buffer = audioContext.createBuffer(1, length, sampleRate);
|
|
const data = buffer.getChannelData(0);
|
|
|
|
let sum = 0;
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
const t = i / sampleRate;
|
|
const phase = (t * frequency) % 1;
|
|
const value = rippleShape(phase);
|
|
|
|
data[i] = value;
|
|
sum += value;
|
|
}
|
|
|
|
/*
|
|
Ta bort likspänningskomponenten.
|
|
En verklig utspänning skulle t.ex. vara 12 V DC plus litet rippel.
|
|
Högtalaren ska dock bara få den hörbara växelkomponenten.
|
|
*/
|
|
const mean = sum / length;
|
|
|
|
let maxAbs = 0;
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
data[i] -= mean;
|
|
maxAbs = Math.max(maxAbs, Math.abs(data[i]));
|
|
}
|
|
|
|
// normalisera
|
|
if (maxAbs > 0) {
|
|
for (let i = 0; i < length; i++) {
|
|
data[i] /= maxAbs;
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
function playRippleTone(frequency) {
|
|
ensureAudioContext();
|
|
stopTone(false);
|
|
|
|
const buffer = createRippleBuffer(frequency);
|
|
|
|
sourceNode = audioContext.createBufferSource();
|
|
gainNode = audioContext.createGain();
|
|
|
|
sourceNode.buffer = buffer;
|
|
sourceNode.loop = true;
|
|
|
|
// måttlig volym med kort fade-in mot knäppar
|
|
gainNode.gain.setValueAtTime(0.0, audioContext.currentTime);
|
|
gainNode.gain.linearRampToValueAtTime(0.2, audioContext.currentTime + 0.03);
|
|
|
|
sourceNode.connect(gainNode);
|
|
gainNode.connect(audioContext.destination);
|
|
|
|
sourceNode.start();
|
|
|
|
document.getElementById("toneStatus").textContent =
|
|
frequency + " Hz formad brumton aktiv.";
|
|
}
|
|
|
|
function stopTone(updateStatus = true) {
|
|
if (sourceNode && gainNode && audioContext) {
|
|
const now = audioContext.currentTime;
|
|
|
|
try {
|
|
gainNode.gain.cancelScheduledValues(now);
|
|
gainNode.gain.setValueAtTime(gainNode.gain.value, now);
|
|
gainNode.gain.linearRampToValueAtTime(0.0, now + 0.03);
|
|
sourceNode.stop(now + 0.04);
|
|
} catch (e) {
|
|
// om redan stoppad
|
|
}
|
|
|
|
sourceNode = null;
|
|
gainNode = null;
|
|
}
|
|
|
|
if (updateStatus) {
|
|
document.getElementById("toneStatus").textContent = "Ingen ton aktiv.";
|
|
}
|
|
}
|
|
</script> |