{"id":18603,"date":"2026-09-17T08:14:56","date_gmt":"2026-09-17T06:14:56","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/lufs-true-peak-loudness-meter\/"},"modified":"2026-09-17T08:19:43","modified_gmt":"2026-09-17T06:19:43","slug":"lufs-true-peak-loudness-meter","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/","title":{"rendered":"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation"},"content":{"rendered":"<div class=\"lw-lufs\" id=\"lufs-wurzel\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<style>\n.lw-lufs { --lufs-warn: #e8a33d; }\n:root[data-theme=\"light\"] .lw-lufs { --lufs-warn: #8f5300; }\n.lw-lufs .lufs-zone { border: 2px dashed var(--border, #2a2a35); border-radius: 8px; padding: 24px 16px; text-align: center; transition: border-color 0.15s, background 0.15s; }\n.lw-lufs .lufs-zone.lufs-ueber { border-color: var(--accent, #2ba6df); background: var(--bg-body, #14141a); }\n.lw-lufs .lufs-kacheln { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 14px 0 4px; }\n.lw-lufs .lufs-kachel { background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 11px 14px; min-width: 0; }\n.lw-lufs .lufs-kachel-name { font-size: 0.74rem; color: var(--text-secondary, #a0a0b0); text-transform: uppercase; letter-spacing: 0.4px; }\n.lw-lufs .lufs-kachel-wert { font-family: monospace; font-size: 1.4rem; font-weight: 700; color: var(--text-primary, #e8e8ee); line-height: 1.25; white-space: nowrap; }\n.lw-lufs .lufs-einheit { font-size: 0.8rem; font-weight: 400; color: var(--text-secondary, #a0a0b0); margin-left: 4px; }\n.lw-lufs .lufs-tabelle { overflow-x: auto; }\n.lw-lufs table { border-collapse: collapse; width: 100%; font-size: 0.86rem; }\n.lw-lufs th, .lw-lufs td { text-align: left; padding: 6px 12px 6px 0; border-bottom: 1px solid var(--border, #2a2a35); vertical-align: top; }\n.lw-lufs th { color: var(--text-primary, #e8e8ee); white-space: nowrap; }\n.lw-lufs td { color: var(--text-secondary, #a0a0b0); }\n.lw-lufs td.lufs-zahl { font-family: monospace; color: var(--text-primary, #e8e8ee); white-space: nowrap; }\n.lw-lufs .lufs-ok { color: var(--ok, #7ee787) !important; }\n.lw-lufs .lufs-warn { color: var(--lufs-warn) !important; }\n.lw-lufs .lufs-fail { color: var(--fail, #ff7b72) !important; }\n.lw-lufs .lufs-balken { height: 8px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 4px; overflow: hidden; }\n.lw-lufs .lufs-balken > div { height: 100%; width: 0; background: var(--accent, #2ba6df); }\n.lw-lufs .lufs-befund { background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-left-width: 3px; border-radius: 8px; padding: 11px 14px; margin: 0 0 8px; }\n.lw-lufs .lufs-h { font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 24px 0 8px; }\n.lw-lufs .lufs-klein { font-size: 0.84rem; color: var(--text-secondary, #a0a0b0); line-height: 1.55; }\n.lw-lufs canvas { display: block; width: 100%; max-width: 100%; }\n.lw-lufs a { color: var(--accent, #2ba6df); }\n@media (max-width: 480px) {\n    .lw-lufs .lufs-kacheln { grid-template-columns: 1fr 1fr; gap: 8px; }\n    .lw-lufs .lufs-kachel { padding: 10px 11px; }\n    .lw-lufs .lufs-kachel-wert { font-size: 1.15rem; }\n}\n<\/style>\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 18px;\">Streaming services do not play a track as loud as it was mastered; they adjust it to a target level. A loud master is turned down, a quiet one is raised only as far as its peak level allows. The meter measures the values this decision rests on and recalculates the normalisation for Spotify and for the AES and EBU recommendations.<\/p>\n<div id=\"lufs-zone\" class=\"lufs-zone\">\n<p style=\"margin: 0 0 12px; color: var(--text-primary, #e8e8ee); font-weight: 700;\">Drop an audio file here<\/p>\n<p>        <button id=\"lufs-knopf\" type=\"button\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 11px 22px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Choose audio file<\/button><br \/>\n        <input id=\"lufs-datei\" type=\"file\" accept=\"audio\/*,.wav,.wave,.aif,.aiff,.aifc,.flac,.mp3,.m4a,.mp4,.aac,.ogg,.oga,.opus\" hidden><\/p>\n<p class=\"lufs-klein\" style=\"margin: 12px 0 0;\">WAV and AIFF are read sample by sample; FLAC, MP3, AAC, Ogg and Opus go through the browser&#039;s decoder.<\/p>\n<\/p><\/div>\n<div class=\"lufs-klein\" style=\"display: flex; flex-wrap: wrap; gap: 10px 24px; align-items: center; margin: 14px 0 0;\">\n        <label style=\"display: inline-flex; gap: 8px; align-items: center; cursor: pointer;\"><input id=\"lufs-dualmono\" type=\"checkbox\" checked> Measure mono as dual mono (as played over two speakers)<\/label><br \/>\n        <label style=\"display: inline-flex; gap: 8px; align-items: center;\">Own target <input id=\"lufs-ziel\" type=\"number\" step=\"0.5\" min=\"-40\" max=\"0\" value=\"-14\" style=\"width: 88px; padding: 6px 8px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px;\"> LUFS<\/label>\n    <\/div>\n<div id=\"lufs-status\" style=\"margin-top: 16px;\" hidden>\n<div id=\"lufs-status-text\" class=\"lufs-klein\" style=\"margin-bottom: 6px;\"><\/div>\n<div class=\"lufs-balken\">\n<div id=\"lufs-fortschritt\"><\/div>\n<\/div><\/div>\n<div id=\"lufs-ausgabe\" style=\"margin-top: 18px;\" aria-live=\"polite\"><\/div>\n<div class=\"lufs-h\">What the values mean<\/div>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">Integrated loudness: the average over the whole file, K-weighted and gated at two thresholds (absolute \u221270 LUFS, relative 10 LU below the average). Normalisation is based on this value.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">True peak: the highest level of the signal between the samples, found by four-times oversampling. It often lies above the sample peak.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">Loudness range (LRA): the distance between the 10th and the 95th percentile of the short-term values according to EBU Tech 3342, a measure of dynamics over time.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">Momentary and short-term: loudness in sliding windows of 0.4 and 3 seconds, searched here on a 10 ms grid.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">PLR: the distance between true peak and integrated loudness. A small value indicates heavily compressed material.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 20px 0 0;\">Sources:<br \/>\n        <a href=\"https:\/\/www.itu.int\/rec\/R-REC-BS.1770\" rel=\"noopener\">ITU-R BS.1770-5<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/tech.ebu.ch\/publications\/r128\" rel=\"noopener\">EBU R 128<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/tech.ebu.ch\/publications\/tech3341\" rel=\"noopener\">EBU Tech 3341<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/tech.ebu.ch\/publications\/tech3342\" rel=\"noopener\">EBU Tech 3342<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/support.spotify.com\/us\/artists\/article\/loudness-normalization\/\" rel=\"noopener\">Spotify for Artists<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/aes.org\/wp-content\/uploads\/2024\/01\/20210924_TD1008_v3.13.pdf\" rel=\"noopener\">AES TD1008<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/www.apple.com\/apple-music\/apple-digital-masters\/docs\/apple-digital-masters.pdf\" rel=\"noopener\">Apple Digital Masters<\/a>\n    <\/p>\n<p class=\"lufs-klein\" style=\"font-size: 0.8rem; margin: 14px 0 0;\">The file is read and measured in the browser and never leaves the device. Measurement follows ITU-R BS.1770-5 (K-weighting, gating, four-times oversampling for true peak) and EBU Tech 3341 and 3342.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Streaming services do not play a track as loud as it was mastered; they adjust it to a target level. A loud master is turned down, a quiet one is raised only as far as its peak level allows. The meter measures the values this decision rests on and recalculates the normalisation for Spotify and for the AES and EBU recommendations.\",\"zone_titel\":\"Drop an audio file here\",\"knopf_datei\":\"Choose audio file\",\"zone_hinweis\":\"WAV and AIFF are read sample by sample; FLAC, MP3, AAC, Ogg and Opus go through the browser\\u0027s decoder.\",\"dualmono\":\"Measure mono as dual mono (as played over two speakers)\",\"eigenes_ziel\":\"Own target\",\"grenze\":\"The file is read and measured in the browser and never leaves the device. Measurement follows ITU-R BS.1770-5 (K-weighting, gating, four-times oversampling for true peak) and EBU Tech 3341 and 3342.\",\"dezimal\":\".\",\"status_lesen\":\"Reading file \u2026\",\"status_dekodieren\":\"Decoding audio \u2026\",\"status_messen\":\"Measuring: {prozent} %\",\"fehler_dekodieren\":\"The file could not be decoded. Either it is damaged or the browser does not support the format; WAV works in every browser.\",\"fehler_webaudio\":\"This browser offers no Web Audio. WAV and AIFF files can still be measured.\",\"fehler_speicher\":\"The browser ran out of memory for this file. A shorter file or a desktop browser gets around this.\",\"fehler_leer\":\"The file contains no audio data.\",\"fehler_format\":\"This WAV or AIFF variant is not supported ({detail}).\",\"kanal_mono\":\"mono\",\"kanal_stereo\":\"stereo\",\"kanal_mehr\":\"{anzahl} channels\",\"bit\":\"{bits}-bit\",\"bit_float\":\"{bits}-bit float\",\"k_integriert\":\"Integrated loudness\",\"k_truepeak\":\"True peak (max.)\",\"k_lra\":\"Loudness range (LRA)\",\"k_mmax\":\"Momentary max.\",\"k_smax\":\"Short-term max.\",\"k_samplepeak\":\"Sample peak (max.)\",\"k_plr\":\"Peak to loudness (PLR)\",\"h_befunde\":\"Findings\",\"h_normalisierung\":\"Normalisation at playback\",\"h_verlauf\":\"Loudness over time\",\"h_erklaerung\":\"What the values mean\",\"befund_tp_ueber_null\":\"True peak above 0 dBTP ({tp} dBTP) || Between the samples the signal exceeds full scale. Conversion to analogue and encoding to AAC or Ogg Vorbis turn this into distortion.\",\"befund_tp_ueber_eins\":\"True peak above \u22121 dBTP ({tp} dBTP) || Spotify, the AES recommendation TD1008 and EBU R 128 name \u22121 dBTP as the ceiling, because lossy encoding raises peaks further.\",\"befund_spotify_zwei\":\"Louder than \u221214 LUFS with a true peak above \u22122 dBTP || For masters louder than \u221214 LUFS, Spotify recommends a true peak below \u22122 dBTP.\",\"befund_voll\":\"{anzahl} samples at or above 0 dBFS || Values at full scale point to clipping in the master or in the encoding.\",\"befund_kurz_lra\":\"Shorter than 60 seconds || According to EBU Tech 3341, the loudness range is not yet stable during the first 60 seconds.\",\"befund_zu_kurz_s\":\"Shorter than 3 seconds || The file is too short for short-term loudness and loudness range.\",\"befund_zu_kurz_i\":\"Shorter than 400 milliseconds || The file is too short for an integrated loudness.\",\"befund_still\":\"No measurable loudness || Every measurement block lies below the absolute gate of \u221270 LUFS.\",\"befund_dualmono\":\"Mono measured as dual mono || The channel counts twice, as in playback over two speakers. Measured as a single channel, the loudness would be 3.01 LU lower.\",\"befund_rate_unbekannt\":\"Sample rate not found in the file header || The browser decoded the file at {rate} Hz. Loudness is barely affected; the sample peak then refers to the converted signal.\",\"befund_gut\":\"Peak level with headroom || True peak at {tp} dBTP: within the recommendations of Spotify, AES and EBU.\",\"spotify_satz_leiser\":\"With default settings, Spotify plays this track {db} dB quieter.\",\"spotify_satz_lauter\":\"With default settings, Spotify raises this track by {db} dB.\",\"spotify_satz_gleich\":\"With default settings, Spotify plays this track practically unchanged.\",\"sp_vorgabe\":\"Preset\",\"sp_ziel\":\"Target\",\"sp_aenderung\":\"Gain change\",\"sp_ergebnis\":\"Result\",\"sp_tp\":\"True peak after\",\"sp_hinweis\":\"Note\",\"ziel_spotify_normal\":\"Spotify, Normal\",\"ziel_spotify_laut\":\"Spotify, Loud\",\"ziel_spotify_leise\":\"Spotify, Quiet\",\"ziel_aes_titel\":\"AES TD1008, single track\",\"ziel_aes_album\":\"AES TD1008, loudest album track\",\"ziel_ebu\":\"EBU R 128 (broadcast)\",\"ziel_eigen\":\"Own target\",\"norm_begrenzt\":\"gain limited: 1 dB of headroom below the true peak\",\"norm_kein_platz\":\"no gain, the true peak leaves no room\",\"norm_limiter\":\"limiter at \u22121 dB engages\",\"norm_ueber\":\"true peak afterwards above \u22121 dBTP\",\"apple_hinweis\":\"Apple Music names no target: according to Apple\\u0027s document \u201cApple Digital Masters\u201d, Sound Check turns tracks up or down, and Apple recommends at least 1 dB of headroom. Spotify normalises albums as a whole, and the AES recommends the same; a quiet album track then stays quiet.\",\"verlauf_hinweis\":\"Short-term loudness (3 s) on a 100 ms grid. Dashed: integrated loudness; dotted: own target.\",\"erkl_integriert\":\"Integrated loudness: the average over the whole file, K-weighted and gated at two thresholds (absolute \u221270 LUFS, relative 10 LU below the average). Normalisation is based on this value.\",\"erkl_truepeak\":\"True peak: the highest level of the signal between the samples, found by four-times oversampling. It often lies above the sample peak.\",\"erkl_lra\":\"Loudness range (LRA): the distance between the 10th and the 95th percentile of the short-term values according to EBU Tech 3342, a measure of dynamics over time.\",\"erkl_momentan\":\"Momentary and short-term: loudness in sliding windows of 0.4 and 3 seconds, searched here on a 10 ms grid.\",\"erkl_plr\":\"PLR: the distance between true peak and integrated loudness. A small value indicates heavily compressed material.\",\"knopf_kopieren\":\"Copy measurements\",\"kopiert\":\"Copied to the clipboard\",\"quellen_titel\":\"Sources\"};<\/p>\n<p>    \/\/ KERN-ANFANG ------------------------------------------------------------\n    \/\/ Messkern nach ITU-R BS.1770-5 (K-Gewichtung, Gating, True Peak) und\n    \/\/ EBU Tech 3341\/3342 (Momentary, Short-term, Loudness Range). Rein\n    \/\/ rechnend, ohne Dokument - derselbe Text laeuft im Browser und in Node.<\/p>\n<p>    \/\/ Tabelle 1 und 2 der Norm, gueltig fuer 48 kHz.\n    var ITU_48K = {\n        stufe1: { b: [1.53512485958697, -2.69169618940638, 1.19839281085285], a: [-1.69065929318241, 0.73248077421585] },\n        stufe2: { b: [1.0, -2.0, 1.0], a: [-1.99004745483398, 0.99007225036621] }\n    };<\/p>\n<p>    \/\/ Anhang 2: 4-fach ueberabtastendes FIR mit 48 Koeffizienten, vier Phasen.\n    var TP_PHASEN = [\n        [0.0017089843750, 0.0109863281250, -0.0196533203125, 0.0332031250000, -0.0594482421875, 0.1373291015625,\n         0.9721679687500, -0.1022949218750, 0.0476074218750, -0.0266113281250, 0.0148925781250, -0.0083007812500],\n        [-0.0291748046875, 0.0292968750000, -0.0517578125000, 0.0891113281250, -0.1665039062500, 0.4650878906250,\n         0.7797851562500, -0.2003173828125, 0.1015625000000, -0.0582275390625, 0.0330810546875, -0.0189208984375],\n        [-0.0189208984375, 0.0330810546875, -0.0582275390625, 0.1015625000000, -0.2003173828125, 0.7797851562500,\n         0.4650878906250, -0.1665039062500, 0.0891113281250, -0.0517578125000, 0.0292968750000, -0.0291748046875],\n        [-0.0083007812500, 0.0148925781250, -0.0266113281250, 0.0476074218750, -0.1022949218750, 0.9721679687500,\n         0.1373291015625, -0.0594482421875, 0.0332031250000, -0.0196533203125, 0.0109863281250, 0.0017089843750]\n    ];<\/p>\n<p>    var ABS_GATE = -70;      \/\/ LUFS, BS.1770 und Tech 3342\n    var REL_GATE_I = -10;    \/\/ LU, integrierte Lautheit\n    var REL_GATE_LRA = -20;  \/\/ LU, Loudness Range\n    var UNTERBLOECKE_JE_S = 100;  \/\/ 10-ms-Raster fuer M und S<\/p>\n<p>    \/\/ Die Norm nennt Koeffizienten nur fuer 48 kHz und verlangt fuer andere\n    \/\/ Raten \"denselben Frequenzgang\". Dafuer wird die bilineare Transformation\n    \/\/ umgekehrt: aus a1\/a2 folgen Vorverzerrung K und Guete Q, aus b0\/b1 die\n    \/\/ beiden Verstaerkungen des Kuhschwanzfilters. Mit K = tan(pi*f0\/fs) entsteht\n    \/\/ daraus fuer jede Rate dasselbe Filter.\n    function vorbild(stufe) {\n        var a1 = stufe.a[0], a2 = stufe.a[1];\n        var K2 = (1 + a1 + a2) \/ (1 - a1 + a2);\n        var K = Math.sqrt(K2);\n        var d = 4 \/ (1 - a1 + a2);\n        var kq = (1 - a2) * d \/ 2;\n        var vh = K2 - stufe.b[1] * d \/ 2;\n        return { f0: Math.atan(K) * 48000 \/ Math.PI, Q: K \/ kq, Vh: vh, Vb: (stufe.b[0] * d - vh - K2) \/ kq };\n    }\n    var VORBILD1 = vorbild(ITU_48K.stufe1);\n    var VORBILD2 = vorbild(ITU_48K.stufe2);<\/p>\n<p>    function kKoeffizienten(fs) {\n        var s = VORBILD1, K = Math.tan(Math.PI * s.f0 \/ fs), K2 = K * K, d = 1 + K \/ s.Q + K2;\n        var st1 = {\n            b: [(s.Vh + s.Vb * K \/ s.Q + K2) \/ d, 2 * (K2 - s.Vh) \/ d, (s.Vh - s.Vb * K \/ s.Q + K2) \/ d],\n            a: [2 * (K2 - 1) \/ d, (1 - K \/ s.Q + K2) \/ d]\n        };\n        s = VORBILD2; K = Math.tan(Math.PI * s.f0 \/ fs); K2 = K * K; d = 1 + K \/ s.Q + K2;\n        \/\/ Zaehler wie in Tabelle 2 unnormiert: die Konstante -0,691 ist auf\n        \/\/ genau diese Verstaerkung bezogen.\n        var st2 = { b: [1, -2, 1], a: [2 * (K2 - 1) \/ d, (1 - K \/ s.Q + K2) \/ d] };\n        return [st1, st2];\n    }<\/p>\n<p>    \/\/ Kanalgewichte nach Tabelle 3. Reihenfolge wie bei WAV und Web Audio:\n    \/\/ L R C LFE Ls Rs. LFE zaehlt nicht mit.\n    function kanalGewichte(anzahl, dualMono) {\n        if (anzahl === 1) { return [dualMono ? 2.0 : 1.0]; }\n        if (anzahl === 2) { return [1, 1]; }\n        if (anzahl === 3) { return [1, 1, 1]; }\n        if (anzahl === 4) { return [1, 1, 1.41, 1.41]; }\n        if (anzahl === 5) { return [1, 1, 1, 1.41, 1.41]; }\n        var g = [1, 1, 1, 0, 1.41, 1.41];\n        for (var i = 6; i < anzahl; i++) { g.push(1.41); }\n        return g;\n    }\n\n    function grenze(k, fs) { return Math.round(k * fs \/ UNTERBLOECKE_JE_S); }\n\n    function messungAnlegen(kanaele, fs, optionen) {\n        optionen = optionen || {};\n        var n = kanaele[0].length;\n        var nUnter = Math.floor(n * UNTERBLOECKE_JE_S \/ fs);\n        while (nUnter > 0 && grenze(nUnter, fs) > n) { nUnter--; }\n        while (grenze(nUnter + 1, fs) <= n) { nUnter++; }\n        var koeff = kKoeffizienten(fs);\n        return {\n            fs: fs, n: n, kanaele: kanaele, anzahl: kanaele.length,\n            gewichte: kanalGewichte(kanaele.length, optionen.dualMono !== false),\n            koeff: koeff, nUnter: nUnter, energieEnde: grenze(nUnter, fs),\n            unterE: new Float64Array(Math.max(nUnter, 1)),\n            kanal: 0, pos: 0, k: 0, akku: 0, z: [0, 0, 0, 0],\n            ring: new Float64Array(24), ringPos: 0,\n            tp: new Float64Array(kanaele.length), sp: new Float64Array(kanaele.length),\n            voll: new Float64Array(kanaele.length)\n        };\n    }\n\n    \/\/ Verarbeitet hoechstens \"budget\" Abtastwerte. Gibt true zurueck, wenn alle\n    \/\/ Kanaele durch sind. So kann die Oberflaeche zwischendurch Luft holen.\n    function messungSchritt(m, budget) {\n        var P0 = TP_PHASEN[0], P1 = TP_PHASEN[1], P2 = TP_PHASEN[2], P3 = TP_PHASEN[3];\n        while (budget > 0 && m.kanal < m.anzahl) {\n            var c = m.kanal, d = m.kanaele[c], g = m.gewichte[c];\n            var st1 = m.koeff[0], st2 = m.koeff[1];\n            var b10 = st1.b[0], b11 = st1.b[1], b12 = st1.b[2], a10 = st1.a[0], a11 = st1.a[1];\n            var b20 = st2.b[0], b21 = st2.b[1], b22 = st2.b[2], a20 = st2.a[0], a21 = st2.a[1];\n            var z1 = m.z[0], z2 = m.z[1], w1 = m.z[2], w2 = m.z[3];\n            var ring = m.ring, rp = m.ringPos;\n            var tp = m.tp[c], sp = m.sp[c], voll = m.voll[c];\n            var k = m.k, akku = m.akku, fs = m.fs, nUnter = m.nUnter;\n            var naechste = grenze(k + 1, fs);\n            var ende = Math.min(m.n, m.pos + budget);\n            var mitEnergie = g > 0;\n            for (var i = m.pos; i < ende; i++) {\n                var x = d[i];\n                var ax = x < 0 ? -x : x;\n                if (ax > sp) { sp = ax; }\n                if (ax >= 1) { voll++; }<\/p>\n<p>                var y1 = b10 * x + z1;\n                z1 = b11 * x - a10 * y1 + z2;\n                z2 = b12 * x - a11 * y1;\n                var y2 = b20 * y1 + w1;\n                w1 = b21 * y1 - a20 * y2 + w2;\n                w2 = b22 * y1 - a21 * y2;<\/p>\n<p>                if (k < nUnter) {\n                    akku += y2 * y2;\n                    if (i + 1 === naechste) {\n                        if (mitEnergie) { m.unterE[k] += g * akku; }\n                        akku = 0; k++; naechste = grenze(k + 1, fs);\n                        \/\/ Abklingende Filterzustaende nicht in den\n                        \/\/ Subnormalbereich laufen lassen - der ist langsam.\n                        if (z1 < 1e-30 &#038;&#038; z1 > -1e-30) { z1 = 0; }\n                        if (z2 < 1e-30 &#038;&#038; z2 > -1e-30) { z2 = 0; }\n                        if (w1 < 1e-30 &#038;&#038; w1 > -1e-30) { w1 = 0; }\n                        if (w2 < 1e-30 &#038;&#038; w2 > -1e-30) { w2 = 0; }\n                    }\n                }<\/p>\n<p>                \/\/ True Peak: Ringpuffer doppelt beschrieben, damit die zwoelf\n                \/\/ letzten Werte zusammenhaengend lesbar sind.\n                ring[rp] = x; ring[rp + 12] = x;\n                var o = rp + 12;\n                var s0 = 0, s1 = 0, s2 = 0, s3 = 0;\n                for (var t = 0; t < 12; t++) {\n                    var v = ring[o - t];\n                    s0 += P0[t] * v; s1 += P1[t] * v; s2 += P2[t] * v; s3 += P3[t] * v;\n                }\n                rp = rp === 11 ? 0 : rp + 1;\n                if (s0 < 0) { s0 = -s0; } if (s1 < 0) { s1 = -s1; } if (s2 < 0) { s2 = -s2; } if (s3 < 0) { s3 = -s3; }\n                if (s0 > tp) { tp = s0; } if (s1 > tp) { tp = s1; } if (s2 > tp) { tp = s2; } if (s3 > tp) { tp = s3; }\n            }\n            budget -= (ende - m.pos);\n            m.pos = ende; m.k = k; m.akku = akku;\n            m.z[0] = z1; m.z[1] = z2; m.z[2] = w1; m.z[3] = w2; m.ringPos = rp;\n            m.tp[c] = tp; m.sp[c] = sp; m.voll[c] = voll;\n            if (m.pos >= m.n) {\n                \/\/ Nachlauf: elf Nullen, damit auch die letzten Zwischenwerte zaehlen.\n                for (var nach = 0; nach < 12; nach++) {\n                    m.ring[m.ringPos] = 0; m.ring[m.ringPos + 12] = 0;\n                    var oo = m.ringPos + 12, q0 = 0, q1 = 0, q2 = 0, q3 = 0;\n                    for (var tt = 0; tt < 12; tt++) {\n                        var vv = m.ring[oo - tt];\n                        q0 += P0[tt] * vv; q1 += P1[tt] * vv; q2 += P2[tt] * vv; q3 += P3[tt] * vv;\n                    }\n                    m.ringPos = m.ringPos === 11 ? 0 : m.ringPos + 1;\n                    m.tp[c] = Math.max(m.tp[c], Math.abs(q0), Math.abs(q1), Math.abs(q2), Math.abs(q3));\n                }\n                m.tp[c] = Math.max(m.tp[c], m.sp[c]);\n                m.kanal++; m.pos = 0; m.k = 0; m.akku = 0;\n                m.z = [0, 0, 0, 0]; m.ring = new Float64Array(24); m.ringPos = 0;\n            }\n        }\n        return m.kanal >= m.anzahl;\n    }<\/p>\n<p>    function db(wert) { return wert > 0 ? 20 * Math.log10(wert) : -Infinity; }\n    function lautheit(energie, abtastwerte) {\n        return energie > 0 && abtastwerte > 0 ? -0.691 + 10 * Math.log10(energie \/ abtastwerte) : -Infinity;\n    }<\/p>\n<p>    \/\/ Fenster aus Unterbloecken: Laenge und Schritt in 10-ms-Einheiten.\n    function fensterWerte(m, laenge, schritt) {\n        var raus = [];\n        for (var k = 0; k + laenge <= m.nUnter; k += schritt) {\n            var e = 0;\n            for (var j = k; j < k + laenge; j++) { e += m.unterE[j]; }\n            raus.push(lautheit(e, grenze(k + laenge, m.fs) - grenze(k, m.fs)));\n        }\n        return raus;\n    }\n\n    function integriert(m) {\n        var bloecke = [];\n        for (var k = 0; k + 40 <= m.nUnter; k += 10) {\n            var e = 0;\n            for (var j = k; j < k + 40; j++) { e += m.unterE[j]; }\n            var anz = grenze(k + 40, m.fs) - grenze(k, m.fs);\n            bloecke.push({ l: lautheit(e, anz), z: e \/ anz });\n        }\n        if (!bloecke.length) { return { wert: null, bloecke: 0, gezaehlt: 0 }; }\n        var summe = 0, anzahl = 0, i;\n        for (i = 0; i < bloecke.length; i++) {\n            if (bloecke[i].l > ABS_GATE) { summe += bloecke[i].z; anzahl++; }\n        }\n        if (!anzahl) { return { wert: -Infinity, bloecke: bloecke.length, gezaehlt: 0 }; }\n        var relativ = -0.691 + 10 * Math.log10(summe \/ anzahl) + REL_GATE_I;\n        summe = 0; anzahl = 0;\n        for (i = 0; i < bloecke.length; i++) {\n            if (bloecke[i].l > ABS_GATE && bloecke[i].l > relativ) { summe += bloecke[i].z; anzahl++; }\n        }\n        return { wert: -0.691 + 10 * Math.log10(summe \/ anzahl), bloecke: bloecke.length, gezaehlt: anzahl, schwelle: relativ };\n    }<\/p>\n<p>    \/\/ Tech 3342, Abschnitt 5, fast zeilengleich.\n    function loudnessRange(kurzzeit) {\n        var abs = kurzzeit.filter(function (s) { return s >= ABS_GATE; });\n        if (!abs.length) { return null; }\n        var leistung = 0;\n        abs.forEach(function (s) { leistung += Math.pow(10, s \/ 10); });\n        var schwelle = 10 * Math.log10(leistung \/ abs.length) + REL_GATE_LRA;\n        var rel = abs.filter(function (s) { return s >= schwelle; }).sort(function (a, b) { return a - b; });\n        var n = rel.length;\n        if (!n) { return null; }\n        var tief = rel[Math.round((n - 1) * 10 \/ 100)];\n        var hoch = rel[Math.round((n - 1) * 95 \/ 100)];\n        return { wert: hoch - tief, p10: tief, p95: hoch, schwelle: schwelle, werte: n };\n    }<\/p>\n<p>    function maxWert(liste) {\n        var m = -Infinity;\n        for (var i = 0; i < liste.length; i++) { if (liste[i] > m) { m = liste[i]; } }\n        return liste.length ? m : null;\n    }<\/p>\n<p>    function messungAuswerten(m) {\n        var momentan = fensterWerte(m, 40, 1);\n        var kurz = fensterWerte(m, 300, 1);\n        var kurzVerlauf = fensterWerte(m, 300, 10);\n        var I = integriert(m);\n        var tpMax = 0, spMax = 0, voll = 0;\n        for (var c = 0; c < m.anzahl; c++) {\n            tpMax = Math.max(tpMax, m.tp[c]); spMax = Math.max(spMax, m.sp[c]); voll += m.voll[c];\n        }\n        return {\n            fs: m.fs, kanaele: m.anzahl, dauer: m.n \/ m.fs,\n            integriert: I.wert, bloecke: I.bloecke, bloeckeGezaehlt: I.gezaehlt,\n            momentanMax: maxWert(momentan), kurzMax: maxWert(kurz),\n            lra: kurzVerlauf.length ? loudnessRange(kurzVerlauf) : null,\n            kurzVerlauf: kurzVerlauf,\n            truePeak: db(tpMax), samplePeak: db(spMax),\n            truePeakKanal: Array.prototype.map.call(m.tp, db),\n            samplePeakKanal: Array.prototype.map.call(m.sp, db),\n            vollAusgesteuert: voll\n        };\n    }\n\n    function analysiere(kanaele, fs, optionen) {\n        var m = messungAnlegen(kanaele, fs, optionen);\n        while (!messungSchritt(m, 1 << 20)) { \/* weiter *\/ }\n        return messungAuswerten(m);\n    }\n\n    \/\/ Normalisierung eines Dienstes nachrechnen. Nur belegte Regeln:\n    \/\/  - Spotify Normal\/Leise: Absenken immer; Anheben nur so weit, dass 1 dB\n    \/\/    Headroom bleibt (Beispiel der Hilfeseite: -20 LUFS, TP -5 -> -16 LUFS).\n    \/\/  - Spotify Laut: Zielwert \"regardless of maximum True Peak\", Limiter\n    \/\/    setzt bei -1 dB ein.\n    \/\/  - alles andere: reine Pegelverschiebung auf den Zielwert.\n    function normalisierung(I, tp, ziel, regel) {\n        if (I === null || !isFinite(I)) { return null; }\n        var wunsch = ziel - I, gain = wunsch, begrenzt = false, limiter = false;\n        if (regel === 'headroom' && wunsch > 0) {\n            var platz = -1 - tp;\n            gain = Math.max(0, Math.min(wunsch, platz));\n            begrenzt = gain < wunsch - 1e-9;\n        }\n        if (regel === 'limiter' &#038;&#038; tp + gain > -1) { limiter = true; }\n        return {\n            gain: gain, ergebnis: I + gain, tpDanach: tp + gain, begrenzt: begrenzt,\n            limiter: limiter, ueberEins: !limiter && tp + gain > -1\n        };\n    }\n    \/\/ KERN-ENDE --------------------------------------------------------------<\/p>\n<p>    function text(schluessel, daten) {\n        var t = T[schluessel];\n        if (typeof t !== 'string') { return null; }\n        return t.replace(\/\\{([a-z_0-9]+)\\}\/g, function (m, k) {\n            return (daten && daten[k] !== undefined && daten[k] !== null) ? String(daten[k]) : m;\n        });\n    }<\/p>\n<p>    \/\/ Zahl mit Komma oder Punkt je Sprache und echtem Minuszeichen.\n    function zahl(wert, stellen, plus) {\n        if (wert === null || wert === undefined || (typeof wert === 'number' && isNaN(wert))) { return '\u2013'; }\n        if (wert === -Infinity) { return '\u2212\u221e'; }\n        if (wert === Infinity) { return '+\u221e'; }\n        var betrag = Math.abs(wert).toFixed(stellen);\n        var null_ = parseFloat(betrag) === 0;\n        var s = betrag.replace('.', T.dezimal);\n        if (wert < 0 &#038;&#038; !null_) { return '\u2212' + s; }\n        if (plus &#038;&#038; wert > 0 && !null_) { return '+' + s; }\n        return s;\n    }\n    function zahlAscii(wert, stellen) {\n        if (wert === null || wert === undefined || !isFinite(wert)) { return wert === -Infinity ? '-inf' : '-'; }\n        return wert.toFixed(stellen);\n    }<\/p>\n<p>    \/\/ --- Dateien lesen -------------------------------------------------------\n    \/\/ WAV und AIFF werden selbst gelesen: so bleibt jeder Abtastwert, wie er in\n    \/\/ der Datei steht, und der Browser rechnet nicht auf seine eigene Rate um.\n    function ascii(dv, o, l) {\n        var s = '';\n        for (var i = 0; i < l &#038;&#038; o + i < dv.byteLength; i++) { s += String.fromCharCode(dv.getUint8(o + i)); }\n        return s;\n    }\n\n    function pcmZuKanaele(puffer, start, laenge, kanaele, bytes, art, kleinEndian) {\n        var rahmen = Math.floor(laenge \/ (bytes * kanaele));\n        var raus = [], c, i;\n        for (c = 0; c < kanaele; c++) { raus.push(new Float32Array(rahmen)); }\n        var ausgerichtet = kleinEndian &#038;&#038; (art === 'int' &#038;&#038; (bytes === 2 || bytes === 4) || art === 'float');\n        if (ausgerichtet) {\n            var stueck = puffer.slice(start, start + rahmen * bytes * kanaele);\n            var ansicht = art === 'float' ? (bytes === 4 ? new Float32Array(stueck) : new Float64Array(stueck))\n                : (bytes === 2 ? new Int16Array(stueck) : new Int32Array(stueck));\n            var teiler = art === 'float' ? 1 : (bytes === 2 ? 32768 : 2147483648);\n            for (c = 0; c < kanaele; c++) {\n                var ziel = raus[c];\n                for (i = 0; i < rahmen; i++) { ziel[i] = ansicht[i * kanaele + c] \/ teiler; }\n            }\n            return raus;\n        }\n        var dv = new DataView(puffer, start, rahmen * bytes * kanaele);\n        var u8 = new Uint8Array(puffer, start, rahmen * bytes * kanaele);\n        var o = 0;\n        for (i = 0; i < rahmen; i++) {\n            for (c = 0; c < kanaele; c++) {\n                var v;\n                if (art === 'float') { v = bytes === 4 ? dv.getFloat32(o, kleinEndian) : dv.getFloat64(o, kleinEndian); }\n                else if (bytes === 1) { v = kleinEndian ? (u8[o] - 128) \/ 128 : dv.getInt8(o) \/ 128; }\n                else if (bytes === 2) { v = dv.getInt16(o, kleinEndian) \/ 32768; }\n                else if (bytes === 3) {\n                    var w = kleinEndian ? (u8[o] | (u8[o + 1] << 8) | (u8[o + 2] << 16)) : ((u8[o] << 16) | (u8[o + 1] << 8) | u8[o + 2]);\n                    if (w &#038; 0x800000) { w -= 0x1000000; }\n                    v = w \/ 8388608;\n                } else { v = dv.getInt32(o, kleinEndian) \/ 2147483648; }\n                raus[c][i] = v;\n                o += bytes;\n            }\n        }\n        return raus;\n    }\n\n    function wavLesen(puffer) {\n        var dv = new DataView(puffer), n = puffer.byteLength, o = 12, fmt = null, ds64 = null;\n        while (o + 8 <= n) {\n            var id = ascii(dv, o, 4), groesse = dv.getUint32(o + 4, true);\n            if (id === 'ds64' &#038;&#038; groesse >= 16) {\n                ds64 = dv.getUint32(o + 16, true) + dv.getUint32(o + 20, true) * 4294967296;\n            } else if (id === 'fmt ') {\n                var tag = dv.getUint16(o + 8, true);\n                if (tag === 0xFFFE && groesse >= 26) { tag = dv.getUint16(o + 32, true); }\n                fmt = { tag: tag, kanaele: dv.getUint16(o + 10, true), rate: dv.getUint32(o + 12, true), bits: dv.getUint16(o + 22, true) };\n            } else if (id === 'data') {\n                if (!fmt) { return { fehler: 'fmt' }; }\n                var laenge = (groesse === 0xFFFFFFFF && ds64 !== null) ? ds64 : groesse;\n                laenge = Math.min(laenge, n - o - 8);\n                var bytes = fmt.bits \/ 8;\n                var art = fmt.tag === 1 ? 'int' : (fmt.tag === 3 ? 'float' : null);\n                if (!art || (art === 'int' && [1, 2, 3, 4].indexOf(bytes) === -1) || (art === 'float' && bytes !== 4 && bytes !== 8) || !fmt.kanaele) {\n                    return { fehler: 'tag ' + fmt.tag + ', ' + fmt.bits + ' bit' };\n                }\n                return { format: 'WAV', rate: fmt.rate, bits: fmt.bits, gleitkomma: art === 'float',\n                    kanaele: pcmZuKanaele(puffer, o + 8, laenge, fmt.kanaele, bytes, art, true) };\n            }\n            if (groesse === 0xFFFFFFFF) { break; }\n            o += 8 + groesse + (groesse % 2);\n        }\n        return { fehler: 'data' };\n    }<\/p>\n<p>    function erweitert80(dv, o) {\n        var exp = dv.getUint16(o, false) & 0x7FFF, hi = dv.getUint32(o + 2, false), lo = dv.getUint32(o + 6, false);\n        if (!exp && !hi && !lo) { return 0; }\n        return (hi * 4294967296 + lo) * Math.pow(2, exp - 16383 - 63);\n    }<\/p>\n<p>    function aiffLesen(puffer, aifc) {\n        var dv = new DataView(puffer), n = puffer.byteLength, o = 12, comm = null;\n        while (o + 8 <= n) {\n            var id = ascii(dv, o, 4), groesse = dv.getUint32(o + 4, false);\n            if (id === 'COMM') {\n                comm = { kanaele: dv.getUint16(o + 8, false), bits: dv.getUint16(o + 14, false), rate: Math.round(erweitert80(dv, o + 16)), art: 'NONE' };\n                if (aifc &#038;&#038; groesse >= 22) { comm.art = ascii(dv, o + 26, 4); }\n            } else if (id === 'SSND') {\n                if (!comm) { return { fehler: 'COMM' }; }\n                var versatz = dv.getUint32(o + 8, false), start = o + 16 + versatz;\n                var laenge = Math.min(groesse - 8 - versatz, n - start);\n                var art = comm.art, bytes = Math.ceil(comm.bits \/ 8);\n                if (art === 'NONE' || art === 'twos') {\n                    return { format: 'AIFF', rate: comm.rate, bits: comm.bits, kanaele: pcmZuKanaele(puffer, start, laenge, comm.kanaele, bytes, 'int', false) };\n                }\n                if (art === 'sowt') {\n                    return { format: 'AIFF', rate: comm.rate, bits: comm.bits, kanaele: pcmZuKanaele(puffer, start, laenge, comm.kanaele, bytes, 'int', true) };\n                }\n                if (art === 'fl32' || art === 'FL32' || art === 'fl64' || art === 'FL64') {\n                    return { format: 'AIFF', rate: comm.rate, bits: art.slice(2) === '32' ? 32 : 64, gleitkomma: true,\n                        kanaele: pcmZuKanaele(puffer, start, laenge, comm.kanaele, art.slice(2) === '32' ? 4 : 8, 'float', false) };\n                }\n                return { fehler: art };\n            }\n            o += 8 + groesse + (groesse % 2);\n        }\n        return { fehler: 'SSND' };\n    }<\/p>\n<p>    \/\/ Abtastrate komprimierter Formate aus dem Kopf, damit der Decoder des\n    \/\/ Browsers nicht auf eine fremde Rate umrechnet.\n    function kopfLesen(puffer) {\n        var n = puffer.byteLength, dv = new DataView(puffer);\n        var b = new Uint8Array(puffer, 0, Math.min(n, 4 << 20));\n        var k4 = ascii(dv, 0, 4);\n        if (k4 === 'fLaC' &#038;&#038; n > 22) {\n            return { format: 'FLAC', rate: (b[18] << 12) | (b[19] << 4) | (b[20] >> 4), kanaeleKopf: ((b[20] >> 1) & 7) + 1, bits: (((b[20] & 1) << 4) | (b[21] >> 4)) + 1 };\n        }\n        if (k4 === 'OggS' && n > 64) {\n            var d = 27 + b[26];\n            if (ascii(dv, d + 1, 6) === 'vorbis' && b[d] === 1) { return { format: 'Ogg Vorbis', rate: dv.getUint32(d + 12, true), kanaeleKopf: b[d + 11] }; }\n            if (ascii(dv, d, 8) === 'OpusHead') { return { format: 'Opus', rate: 48000, kanaeleKopf: b[d + 9] }; }\n            if (ascii(dv, d + 1, 4) === 'FLAC' && ascii(dv, d + 9, 4) === 'fLaC') {\n                var s = d + 17;\n                return { format: 'Ogg FLAC', rate: (b[s + 10] << 12) | (b[s + 11] << 4) | (b[s + 12] >> 4) };\n            }\n            return { format: 'Ogg', rate: null };\n        }\n        if (ascii(dv, 4, 4) === 'ftyp') {\n            var suchen = function (a, bb, c, dd) {\n                for (var i = 4; i + 36 < b.length; i++) {\n                    if (b[i] === a &#038;&#038; b[i + 1] === bb &#038;&#038; b[i + 2] === c &#038;&#038; b[i + 3] === dd) { return i - 4; }\n                }\n                return -1;\n            };\n            var box = suchen(0x6D, 0x70, 0x34, 0x61), name = 'AAC';\n            if (box < 0) { box = suchen(0x61, 0x6C, 0x61, 0x63); name = 'ALAC'; }\n            var rate = box >= 0 ? dv.getUint16(box + 32, false) : 0;\n            return { format: name, rate: rate >= 8000 ? rate : null };\n        }\n        var start = 0;\n        if (ascii(dv, 0, 3) === 'ID3' && n > 10) {\n            start = 10 + ((b[6] & 0x7f) << 21 | (b[7] &#038; 0x7f) << 14 | (b[8] &#038; 0x7f) << 7 | (b[9] &#038; 0x7f)) + ((b[5] &#038; 0x10) ? 10 : 0);\n        }\n        var bis = Math.min(b.length - 4, start + 65536);\n        for (var j = start; j < bis; j++) {\n            if (b[j] !== 0xFF || (b[j + 1] &#038; 0xE0) !== 0xE0) { continue; }\n            var version = (b[j + 1] >> 3) & 3, schicht = (b[j + 1] >> 1) & 3;\n            if (schicht === 0 && (b[j + 1] & 0xF6) === 0xF0) {\n                var idx = (b[j + 2] >> 2) & 15;\n                var raten = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];\n                if (idx < raten.length) { return { format: 'AAC', rate: raten[idx] }; }\n                continue;\n            }\n            var ri = (b[j + 2] >> 2) & 3, bri = b[j + 2] >> 4;\n            if (version === 1 || schicht === 0 || ri === 3 || bri === 15 || bri === 0) { continue; }\n            var basis = [44100, 48000, 32000][ri];\n            return { format: schicht === 1 ? 'MP3' : 'MPEG', rate: version === 3 ? basis : (version === 2 ? basis \/ 2 : basis \/ 4), kanaeleKopf: (b[j + 3] >> 6) === 3 ? 1 : 2 };\n        }\n        return { format: null, rate: null };\n    }<\/p>\n<p>    function dekodieren(puffer, rate) {\n        return new Promise(function (ok, fehler) {\n            var Ctx = window.OfflineAudioContext || window.webkitOfflineAudioContext;\n            if (!Ctx) { fehler(new Error('webaudio')); return; }\n            var ctx;\n            try { ctx = new Ctx(1, 1, rate); } catch (e) { ctx = new Ctx(1, 1, 48000); }\n            var erledigt = false;\n            function gut(buf) { if (!erledigt) { erledigt = true; ok(buf); } }\n            function schlecht(e) { if (!erledigt) { erledigt = true; fehler(e || new Error('dekodieren')); } }\n            try {\n                var p = ctx.decodeAudioData(puffer, gut, schlecht);\n                if (p && typeof p.then === 'function') { p.then(gut, schlecht); }\n            } catch (e) { schlecht(e); }\n        });\n    }<\/p>\n<p>    \/\/ Einlesen: gibt {format, rate, bits, kanaele[], geschaetzt} zurueck.\n    function einlesen(puffer) {\n        var dv = new DataView(puffer), k4 = ascii(dv, 0, 4), art = ascii(dv, 8, 4);\n        if ((k4 === 'RIFF' || k4 === 'RF64' || k4 === 'BW64') && art === 'WAVE') {\n            var w = wavLesen(puffer);\n            if (!w.fehler) { return Promise.resolve(w); }\n            return Promise.reject({ format: w.fehler });\n        }\n        if (k4 === 'FORM' && (art === 'AIFF' || art === 'AIFC')) {\n            var a = aiffLesen(puffer, art === 'AIFC');\n            if (!a.fehler) { return Promise.resolve(a); }\n            \/\/ komprimiertes AIFC: vielleicht kann es der Browser\n        }\n        var kopf = kopfLesen(puffer);\n        var rate = kopf.rate && kopf.rate >= 3000 && kopf.rate <= 768000 ? kopf.rate : 48000;\n        return dekodieren(puffer, rate).then(function (buf) {\n            var kanaele = [];\n            for (var c = 0; c < buf.numberOfChannels; c++) { kanaele.push(buf.getChannelData(c)); }\n            return { format: kopf.format, rate: buf.sampleRate, bits: kopf.bits || null, kanaele: kanaele, geschaetzt: !kopf.rate };\n        });\n    }\n\n    \/\/ --- Messen in Scheiben ------------------------------------------------\n    var tick = typeof MessageChannel !== 'undefined' ? new MessageChannel() : null, schlange = [];\n    if (tick) { tick.port1.onmessage = function () { var f = schlange.shift(); if (f) { f(); } }; }\n    function spaeter(f) { if (tick) { schlange.push(f); tick.port2.postMessage(0); } else { setTimeout(f, 0); } }\n\n    function messenAsync(kanaele, fs, optionen, fortschritt) {\n        return new Promise(function (ok, fehler) {\n            var m;\n            try { m = messungAnlegen(kanaele, fs, optionen); } catch (e) { fehler(e); return; }\n            var gesamt = m.n * m.anzahl || 1;\n            function schritt() {\n                try {\n                    var fertig = messungSchritt(m, 1 << 18);\n                    if (fortschritt) { fortschritt(Math.min(1, (m.kanal * m.n + m.pos) \/ gesamt)); }\n                    if (fertig) { ok(messungAuswerten(m)); } else { spaeter(schritt); }\n                } catch (e) { fehler(e); }\n            }\n            spaeter(schritt);\n        });\n    }\n\n    \/\/ --- Bewertung -----------------------------------------------------------\n    var ZIELE = [\n        { id: 'spotify_normal', ziel: -14, regel: 'headroom' },\n        { id: 'spotify_laut', ziel: -11, regel: 'limiter' },\n        { id: 'spotify_leise', ziel: -19, regel: 'headroom' },\n        { id: 'aes_titel', ziel: -16, regel: 'frei' },\n        { id: 'aes_album', ziel: -14, regel: 'frei' },\n        { id: 'ebu', ziel: -23, regel: 'frei' },\n        { id: 'eigen', ziel: null, regel: 'frei' }\n    ];\n\n    \/\/ Geurteilt wird ueber den Wert, der auch angezeigt wird (eine Nachkommastelle):\n    \/\/ sonst steht bei -0,98 dBTP \"-1,0 dBTP\" neben dem Hinweis \"ueber -1 dBTP\".\n    function r1(x) { return (typeof x === 'number' &#038;&#038; isFinite(x)) ? Math.round(x * 10) \/ 10 : x; }\n\n    function befunde(e, info) {\n        var b = [];\n        function dazu(key, stufe, daten) { b.push({ key: key, stufe: stufe, daten: daten || {} }); }\n        var tp = r1(e.truePeak), I = r1(e.integriert);\n        if (e.dauer < 0.4) { dazu('zu_kurz_i', 'info'); }\n        else if (e.dauer < 3) { dazu('zu_kurz_s', 'info'); }\n        if (e.integriert === -Infinity) { dazu('still', 'info'); }\n        if (isFinite(tp) &#038;&#038; tp > 0) { dazu('tp_ueber_null', 'kritisch', { tp: zahl(tp, 1) }); }\n        else if (isFinite(tp) && tp > -1) { dazu('tp_ueber_eins', 'warnung', { tp: zahl(tp, 1) }); }\n        if (I !== null && isFinite(I) && I > -14 && tp > -2) { dazu('spotify_zwei', 'warnung'); }\n        if (e.vollAusgesteuert > 0) { dazu('voll', 'warnung', { anzahl: e.vollAusgesteuert }); }\n        if (e.dauer >= 3 && e.dauer < 60) { dazu('kurz_lra', 'info'); }\n        if (info &#038;&#038; info.dualMono) { dazu('dualmono', 'info'); }\n        if (info &#038;&#038; info.geschaetzt) { dazu('rate_unbekannt', 'info', { rate: e.fs }); }\n        if (isFinite(tp) &#038;&#038; tp <= -1 &#038;&#038; !(I !== null &#038;&#038; I > -14 && tp > -2) && e.vollAusgesteuert === 0) {\n            dazu('gut', 'gut', { tp: zahl(tp, 1) });\n        }\n        return b;\n    }<\/p>\n<p>    function eigenesZiel() {\n        var feld = document.getElementById('lufs-ziel');\n        var v = feld ? parseFloat(String(feld.value).replace(',', '.')) : NaN;\n        return isFinite(v) ? Math.max(-60, Math.min(0, v)) : -14;\n    }<\/p>\n<p>    function tabelle(e) {\n        return ZIELE.map(function (z) {\n            var ziel = z.ziel === null ? eigenesZiel() : z.ziel;\n            return { id: z.id, ziel: ziel, regel: z.regel, n: normalisierung(e.integriert, e.truePeak, ziel, z.regel) };\n        });\n    }<\/p>\n<p>    \/\/ --- Darstellung --------------------------------------------------------\n    var wurzel = document.getElementById('lufs-wurzel');\n    var ausgabe = document.getElementById('lufs-ausgabe');\n    var letztes = null;<\/p>\n<p>    function el(tag, klasse, txt) {\n        var e = document.createElement(tag);\n        if (klasse) { e.className = klasse; }\n        if (txt !== undefined && txt !== null) { e.textContent = txt; }\n        return e;\n    }<\/p>\n<p>    function kachel(ziel, name, wert, einheit, klasse) {\n        var k = el('div', 'lufs-kachel');\n        k.appendChild(el('div', 'lufs-kachel-name', name));\n        var w = el('div', 'lufs-kachel-wert' + (klasse ? ' ' + klasse : ''), wert);\n        if (einheit) { w.appendChild(el('span', 'lufs-einheit', einheit)); }\n        k.appendChild(w);\n        ziel.appendChild(k);\n        return k;\n    }<\/p>\n<p>    function dauerText(s) {\n        var min = Math.floor(s \/ 60), sek = s - min * 60;\n        return min + ':' + (sek < 10 ? '0' : '') + sek.toFixed(1).replace('.', T.dezimal);\n    }\n\n    function kanalText(n) { return n === 1 ? T.kanal_mono : (n === 2 ? T.kanal_stereo : text('kanal_mehr', { anzahl: n })); }\n\n    var STUFEN_KLASSE = { kritisch: 'lufs-fail', warnung: 'lufs-warn', gut: 'lufs-ok', info: '' };\n\n    function darstellen(e, info) {\n        letztes = { e: e, info: info || {} };\n        info = letztes.info;\n        ausgabe.innerHTML = '';\n\n        var kopf = [];\n        if (info.name) { kopf.push(info.name); }\n        if (info.format) { kopf.push(info.format); }\n        kopf.push(e.fs + ' Hz');\n        kopf.push(kanalText(e.kanaele));\n        if (info.bits) { kopf.push(text(info.gleitkomma ? 'bit_float' : 'bit', { bits: info.bits })); }\n        kopf.push(dauerText(e.dauer));\n        ausgabe.appendChild(el('p', 'lufs-klein', kopf.join(' \u00b7 ')));\n\n        var kacheln = el('div', 'lufs-kacheln');\n        var tp = e.truePeak;\n        kachel(kacheln, T.k_integriert, zahl(e.integriert, 1), 'LUFS');\n        kachel(kacheln, T.k_truepeak, zahl(tp, 1), 'dBTP', !isFinite(tp) ? '' : (r1(tp) > 0 ? 'lufs-fail' : (r1(tp) > -1 ? 'lufs-warn' : 'lufs-ok')));\n        kachel(kacheln, T.k_lra, e.lra ? zahl(e.lra.wert, 1) : '\u2013', 'LU');\n        kachel(kacheln, T.k_mmax, zahl(e.momentanMax, 1), 'LUFS');\n        kachel(kacheln, T.k_smax, zahl(e.kurzMax, 1), 'LUFS');\n        kachel(kacheln, T.k_samplepeak, zahl(e.samplePeak, 1), 'dBFS');\n        var plr = (e.integriert !== null && isFinite(e.integriert) && isFinite(tp)) ? tp - e.integriert : null;\n        kachel(kacheln, T.k_plr, zahl(plr, 1), 'dB');\n        ausgabe.appendChild(kacheln);<\/p>\n<p>        \/\/ Spotify in einem Satz\n        var zeilen = tabelle(e), normal = zeilen[0].n;\n        if (normal) {\n            var satz = normal.gain < -0.05 ? text('spotify_satz_leiser', { db: zahl(-normal.gain, 1) })\n                : (normal.gain > 0.05 ? text('spotify_satz_lauter', { db: zahl(normal.gain, 1) }) : T.spotify_satz_gleich);\n            ausgabe.appendChild(el('p', '', satz)).setAttribute('style', 'margin: 12px 0 0; color: var(--text-primary, #e8e8ee); font-weight: 700;');\n        }<\/p>\n<p>        ausgabe.appendChild(el('div', 'lufs-h', T.h_befunde));\n        befunde(e, info).forEach(function (f) {\n            var k = el('div', 'lufs-befund');\n            var farbe = { kritisch: 'var(--fail, #ff7b72)', warnung: 'var(--lufs-warn)', gut: 'var(--ok, #7ee787)', info: 'var(--border, #2a2a35)' }[f.stufe];\n            k.style.borderLeftColor = farbe;\n            var t = (text('befund_' + f.key, f.daten) || f.key).split(' || ');\n            k.appendChild(el('div', STUFEN_KLASSE[f.stufe], t[0])).setAttribute('style', 'font-weight: 700; font-size: 0.9rem; color: var(--text-primary, #e8e8ee);');\n            if (t[1]) { k.appendChild(el('div', 'lufs-klein', t[1])).style.paddingTop = '4px'; }\n            ausgabe.appendChild(k);\n        });<\/p>\n<p>        ausgabe.appendChild(el('div', 'lufs-h', T.h_normalisierung));\n        var huelle = el('div', 'lufs-tabelle'), tab = el('table'), kz = el('tr');\n        [T.sp_vorgabe, T.sp_ziel, T.sp_aenderung, T.sp_ergebnis, T.sp_tp, T.sp_hinweis].forEach(function (s) { kz.appendChild(el('th', '', s)); });\n        var thead = el('thead'); thead.appendChild(kz); tab.appendChild(thead);\n        var tbody = el('tbody');\n        zeilen.forEach(function (z) {\n            var tr = el('tr');\n            tr.setAttribute('data-ziel', z.id);\n            tr.appendChild(el('td', '', T['ziel_' + z.id]));\n            tr.appendChild(el('td', 'lufs-zahl', zahl(z.ziel, 1) + ' LUFS'));\n            if (!z.n) {\n                ['\u2013', '\u2013', '\u2013', ''].forEach(function (s) { tr.appendChild(el('td', 'lufs-zahl', s)); });\n            } else {\n                tr.appendChild(el('td', 'lufs-zahl', zahl(z.n.gain, 1, true) + ' dB'));\n                tr.appendChild(el('td', 'lufs-zahl', zahl(z.n.ergebnis, 1) + ' LUFS'));\n                var tpD = r1(z.n.tpDanach);\n                tr.appendChild(el('td', 'lufs-zahl' + (tpD > -1 ? ' lufs-warn' : ''), zahl(z.n.tpDanach, 1) + ' dBTP'));\n                var hinweis = [];\n                if (z.n.begrenzt && z.n.gain > 0) { hinweis.push(T.norm_begrenzt); }\n                if (z.n.begrenzt && z.n.gain === 0) { hinweis.push(T.norm_kein_platz); }\n                if (z.regel === 'limiter' && tpD > -1) { hinweis.push(T.norm_limiter); }\n                if (z.regel !== 'limiter' && tpD > -1) { hinweis.push(T.norm_ueber); }\n                tr.appendChild(el('td', '', hinweis.join('; ')));\n            }\n            tbody.appendChild(tr);\n        });\n        tab.appendChild(tbody);\n        huelle.appendChild(tab);\n        ausgabe.appendChild(huelle);\n        ausgabe.appendChild(el('p', 'lufs-klein', T.apple_hinweis)).style.marginTop = '10px';<\/p>\n<p>        ausgabe.appendChild(el('div', 'lufs-h', T.h_verlauf));\n        var leinwand = el('canvas');\n        leinwand.id = 'lufs-verlauf';\n        leinwand.setAttribute('role', 'img');\n        leinwand.setAttribute('aria-label', T.h_verlauf);\n        ausgabe.appendChild(leinwand);\n        ausgabe.appendChild(el('p', 'lufs-klein', T.verlauf_hinweis)).style.marginTop = '6px';\n        verlaufZeichnen();<\/p>\n<p>        var kopieren = el('button', 'button', T.knopf_kopieren);\n        kopieren.type = 'button';\n        kopieren.setAttribute('style', 'margin-top: 14px; background: transparent; color: var(--accent, #2ba6df); border: 1px solid var(--accent, #2ba6df); padding: 8px 16px; border-radius: 6px; font-weight: 700; cursor: pointer;');\n        var meldung = el('span', 'lufs-klein');\n        meldung.style.marginLeft = '12px';\n        kopieren.addEventListener('click', function () {\n            var bericht = berichtText(e, info);\n            var fertig = function () { meldung.textContent = T.kopiert; };\n            if (navigator.clipboard && navigator.clipboard.writeText) {\n                navigator.clipboard.writeText(bericht).then(fertig, function () { ersatzKopieren(bericht); fertig(); });\n            } else { ersatzKopieren(bericht); fertig(); }\n        });\n        ausgabe.appendChild(kopieren);\n        ausgabe.appendChild(meldung);\n    }<\/p>\n<p>    function ersatzKopieren(s) {\n        var ta = document.createElement('textarea');\n        ta.value = s; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.opacity = '0';\n        document.body.appendChild(ta); ta.select();\n        try { document.execCommand('copy'); } catch (x) { \/* nichts *\/ }\n        document.body.removeChild(ta);\n    }<\/p>\n<p>    function berichtText(e, info) {\n        var z = [];\n        z.push((info.name || '') + ' (' + e.fs + ' Hz, ' + kanalText(e.kanaele) + ', ' + dauerText(e.dauer) + ')');\n        z.push(T.k_integriert + ': ' + zahlAscii(e.integriert, 1) + ' LUFS');\n        z.push(T.k_truepeak + ': ' + zahlAscii(e.truePeak, 1) + ' dBTP');\n        z.push(T.k_lra + ': ' + (e.lra ? zahlAscii(e.lra.wert, 1) : '-') + ' LU');\n        z.push(T.k_mmax + ': ' + zahlAscii(e.momentanMax, 1) + ' LUFS');\n        z.push(T.k_smax + ': ' + zahlAscii(e.kurzMax, 1) + ' LUFS');\n        z.push(T.k_samplepeak + ': ' + zahlAscii(e.samplePeak, 1) + ' dBFS');\n        tabelle(e).forEach(function (r) {\n            if (r.n) { z.push(T['ziel_' + r.id] + ' (' + zahlAscii(r.ziel, 1) + ' LUFS): ' + (r.n.gain > 0 ? '+' : '') + zahlAscii(r.n.gain, 1) + ' dB -> ' + zahlAscii(r.n.ergebnis, 1) + ' LUFS'); }\n        });\n        return z.join('\\n');\n    }<\/p>\n<p>    function farbe(name, rueckfall) {\n        var v = wurzel ? getComputedStyle(wurzel).getPropertyValue(name) : '';\n        return (v && v.trim()) || rueckfall;\n    }<\/p>\n<p>    function verlaufZeichnen() {\n        var leinwand = document.getElementById('lufs-verlauf');\n        if (!leinwand || !letztes) { return; }\n        var e = letztes.e, werte = e.kurzVerlauf;\n        var breite = Math.max(260, Math.round(leinwand.parentNode.clientWidth || 600)), hoehe = 210;\n        var dpr = window.devicePixelRatio || 1;\n        leinwand.width = Math.round(breite * dpr); leinwand.height = Math.round(hoehe * dpr);\n        leinwand.style.height = hoehe + 'px';\n        var g = leinwand.getContext && leinwand.getContext('2d');\n        if (!g) { return; }\n        g.setTransform(dpr, 0, 0, dpr, 0, 0);\n        g.clearRect(0, 0, breite, hoehe);\n        var cText = farbe('--text-secondary', '#94a3b8'), cLinie = farbe('--border', '#2d3748'),\n            cAkzent = farbe('--accent', '#2ba6df'), cStark = farbe('--text-primary', '#e0e6ed'), cWarn = farbe('--lufs-warn', '#e8a33d');\n        var L = 46, R = 10, O = 8, U = 24, w = breite - L - R, h = hoehe - O - U;\n        var ziel = eigenesZiel(), I = e.integriert !== null && isFinite(e.integriert) ? e.integriert : null;\n        var tief = Infinity, hoch = -Infinity;\n        for (var i = 0; i < werte.length; i++) { if (isFinite(werte[i])) { tief = Math.min(tief, werte[i]); hoch = Math.max(hoch, werte[i]); } }\n        var yMax = Math.max(0, Math.ceil(((isFinite(hoch) ? hoch : -20) + 1) \/ 6) * 6);\n        var unten = Math.min(isFinite(tief) ? tief : -40, I !== null ? I - 12 : -40, ziel - 6);\n        var yMin = Math.max(-72, Math.floor(unten \/ 6) * 6);\n        if (yMax - yMin < 18) { yMin = yMax - 18; }\n        var y = function (v) { v = Math.max(yMin, Math.min(yMax, v)); return O + (yMax - v) \/ (yMax - yMin) * h; };\n        g.font = '11px monospace'; g.lineWidth = 1; g.textBaseline = 'middle';\n        var stufe = (yMax - yMin) > 48 ? 12 : 6;\n        for (var t = yMin; t <= yMax; t += stufe) {\n            g.strokeStyle = cLinie; g.beginPath(); g.moveTo(L, Math.round(y(t)) + 0.5); g.lineTo(L + w, Math.round(y(t)) + 0.5); g.stroke();\n            g.fillStyle = cText; g.fillText(zahl(t, 0), 4, y(t));\n        }\n        var dauer = Math.max(e.dauer, 0.001), schritte = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];\n        var sek = schritte[schritte.length - 1];\n        for (var s = 0; s < schritte.length; s++) { if (dauer \/ schritte[s] <= Math.max(3, Math.floor(w \/ 70))) { sek = schritte[s]; break; } }\n        g.textBaseline = 'top';\n        for (var z = 0; z <= dauer + 1e-9; z += sek) {\n            var xz = L + z \/ dauer * w;\n            g.fillStyle = cText; g.fillText(sek >= 60 || dauer >= 60 ? Math.floor(z \/ 60) + ':' + ((z % 60) < 10 ? '0' : '') + Math.floor(z % 60) : z + ' s', Math.min(xz, L + w - 30), O + h + 6);\n        }\n        if (werte.length) {\n            g.strokeStyle = cAkzent; g.lineWidth = 1.6; g.beginPath();\n            var offen = false;\n            for (var k = 0; k < werte.length; k++) {\n                var x = L + (3 + k * 0.1) \/ dauer * w;\n                if (!isFinite(werte[k]) || werte[k] < yMin) { offen = false; continue; }\n                if (!offen) { g.moveTo(x, y(werte[k])); offen = true; } else { g.lineTo(x, y(werte[k])); }\n            }\n            g.stroke();\n        }\n        g.lineWidth = 1.2;\n        if (I !== null &#038;&#038; g.setLineDash) {\n            g.setLineDash([6, 4]); g.strokeStyle = cStark; g.beginPath(); g.moveTo(L, y(I)); g.lineTo(L + w, y(I)); g.stroke();\n        }\n        if (g.setLineDash) {\n            g.setLineDash([2, 3]); g.strokeStyle = cWarn; g.beginPath(); g.moveTo(L, y(ziel)); g.lineTo(L + w, y(ziel)); g.stroke();\n            g.setLineDash([]);\n        }\n    }\n\n    \/\/ --- Ablauf ---------------------------------------------------------------\n    var status = document.getElementById('lufs-status');\n    var statusText = document.getElementById('lufs-status-text');\n    var balken = document.getElementById('lufs-fortschritt');\n    var dualmono = document.getElementById('lufs-dualmono');\n    var gelesen = null;\n\n    function zeigeStatus(t, anteil) {\n        if (!status) { return; }\n        status.hidden = false;\n        statusText.textContent = t;\n        balken.style.width = Math.round((anteil || 0) * 100) + '%';\n    }\n    function fehlerZeigen(t) {\n        if (status) { status.hidden = true; }\n        ausgabe.innerHTML = '';\n        var k = el('div', 'lufs-befund lufs-klein', t);\n        k.style.borderLeftColor = 'var(--fail, #ff7b72)';\n        ausgabe.appendChild(k);\n    }\n\n    function messenUndZeigen() {\n        if (!gelesen) { return Promise.resolve(null); }\n        var mono = gelesen.kanaele.length === 1 &#038;&#038; dualmono &#038;&#038; dualmono.checked;\n        zeigeStatus(text('status_messen', { prozent: 0 }), 0);\n        return messenAsync(gelesen.kanaele, gelesen.rate, { dualMono: mono }, function (a) {\n            zeigeStatus(text('status_messen', { prozent: Math.round(a * 100) }), a);\n        }).then(function (e) {\n            status.hidden = true;\n            darstellen(e, { name: gelesen.name, format: gelesen.format, bits: gelesen.bits, gleitkomma: gelesen.gleitkomma, dualMono: mono, geschaetzt: gelesen.geschaetzt });\n            return e;\n        });\n    }\n\n    function verarbeitenPuffer(puffer, name) {\n        zeigeStatus(T.status_dekodieren, 0);\n        return einlesen(puffer).then(function (d) {\n            if (!d.kanaele.length || !d.kanaele[0].length) { throw { leer: true }; }\n            gelesen = { name: name, format: d.format || String(name || '').split('.').pop().toUpperCase(), rate: d.rate, bits: d.bits, gleitkomma: !!d.gleitkomma, kanaele: d.kanaele, geschaetzt: d.geschaetzt };\n            return messenUndZeigen();\n        }).catch(function (f) {\n            gelesen = null;\n            if (window.LW_TEST &#038;&#038; window.LW_TEST.LUFS) { window.LW_TEST.LUFS.letzterFehler = f; }\n            if (window.console &#038;&#038; console.warn) { console.warn(f); }\n            if (f &#038;&#038; f.leer) { fehlerZeigen(T.fehler_leer); }\n            else if (f &#038;&#038; f.format) { fehlerZeigen(text('fehler_format', { detail: f.format })); }\n            else if (f &#038;&#038; f.message === 'webaudio') { fehlerZeigen(T.fehler_webaudio); }\n            else if (f &#038;&#038; (f.name === 'RangeError' || \/memory|allocation\/i.test(String(f.message || '')))) { fehlerZeigen(T.fehler_speicher); }\n            else { fehlerZeigen(T.fehler_dekodieren); }\n            return null;\n        });\n    }\n\n    function dateiNehmen(datei) {\n        if (!datei) { return; }\n        zeigeStatus(T.status_lesen, 0);\n        var leser = new FileReader();\n        leser.onload = function () { verarbeitenPuffer(leser.result, datei.name); };\n        leser.onerror = function () { fehlerZeigen(T.fehler_dekodieren); };\n        try { leser.readAsArrayBuffer(datei); } catch (x) { fehlerZeigen(T.fehler_speicher); }\n    }\n\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.LUFS = {\n        T: T, ZIELE: ZIELE, text: text, zahl: zahl, kKoeffizienten: kKoeffizienten, analysiere: analysiere,\n        messungAnlegen: messungAnlegen, messungSchritt: messungSchritt, messungAuswerten: messungAuswerten,\n        fensterWerte: fensterWerte, loudnessRange: loudnessRange, normalisierung: normalisierung,\n        kopfLesen: kopfLesen, wavLesen: wavLesen, aiffLesen: aiffLesen, befunde: befunde,\n        darstellen: darstellen, verarbeitenPuffer: verarbeitenPuffer, einlesen: einlesen\n    };\n\n    var zone = document.getElementById('lufs-zone'), eingabe = document.getElementById('lufs-datei'), knopf = document.getElementById('lufs-knopf');\n    if (!zone || !eingabe || !ausgabe) { return; }\n    knopf.addEventListener('click', function () { eingabe.click(); });\n    eingabe.addEventListener('change', function () { dateiNehmen(eingabe.files &#038;&#038; eingabe.files[0]); eingabe.value = ''; });\n    ['dragenter', 'dragover'].forEach(function (typ) {\n        zone.addEventListener(typ, function (ev) { ev.preventDefault(); zone.classList.add('lufs-ueber'); });\n    });\n    ['dragleave', 'drop'].forEach(function (typ) {\n        zone.addEventListener(typ, function (ev) { ev.preventDefault(); zone.classList.remove('lufs-ueber'); });\n    });\n    zone.addEventListener('drop', function (ev) {\n        var f = ev.dataTransfer &#038;&#038; ev.dataTransfer.files &#038;&#038; ev.dataTransfer.files[0];\n        dateiNehmen(f);\n    });\n    if (dualmono) {\n        dualmono.addEventListener('change', function () { if (gelesen &#038;&#038; gelesen.kanaele.length === 1) { messenUndZeigen(); } });\n    }\n    var zielFeld = document.getElementById('lufs-ziel');\n    if (zielFeld) {\n        zielFeld.addEventListener('input', function () { if (letztes) { darstellen(letztes.e, letztes.info); } });\n    }\n    var warten = null;\n    window.addEventListener('resize', function () { clearTimeout(warten); warten = setTimeout(verlaufZeichnen, 150); });\n    if (window.MutationObserver) {\n        new MutationObserver(function () { verlaufZeichnen(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });\n    }\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Measures integrated loudness (LUFS), true peak (dBTP) and loudness range of an audio file in the browser, following ITU-R BS.1770-5 and EBU R 128, and recalculates how Spotify and the AES recommendation would normalise it. The file never leaves the device.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92901],"class_list":["post-18603","page","type-page","status-publish","hentry","tag-music-production"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>LUFS and True Peak Meter: loudness, loudness range and streaming normalisation - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Measures integrated loudness (LUFS), true peak (dBTP) and loudness range of an audio file in the browser, following ITU-R BS.1770-5 and EBU R 128, and recalculates how Spotify and the AES recommendation would normalise it. The file never leaves the device.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-17T06:19:43+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/lufs-true-peak-loudness-meter\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/lufs-true-peak-loudness-meter\\\/\",\"name\":\"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-17T06:14:56+00:00\",\"dateModified\":\"2026-09-17T06:19:43+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/lufs-true-peak-loudness-meter\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/lufs-true-peak-loudness-meter\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/lufs-true-peak-loudness-meter\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/","og_locale":"en_US","og_type":"article","og_title":"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation - Lukas Wojcik - Blog","og_description":"Measures integrated loudness (LUFS), true peak (dBTP) and loudness range of an audio file in the browser, following ITU-R BS.1770-5 and EBU R 128, and recalculates how Spotify and the AES recommendation would normalise it. The file never leaves the device.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-09-17T06:19:43+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/","name":"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-17T06:14:56+00:00","dateModified":"2026-09-17T06:19:43+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/lufs-true-peak-loudness-meter\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"LUFS and True Peak Meter: loudness, loudness range and streaming normalisation"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/18603","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=18603"}],"version-history":[{"count":1,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/18603\/revisions"}],"predecessor-version":[{"id":18606,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/18603\/revisions\/18606"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=18603"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=18603"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}