{"id":18605,"date":"2026-09-17T08:15:04","date_gmt":"2026-09-17T06:15:04","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/narzedzia\/lufs-true-peak-loudness-meter\/"},"modified":"2026-09-17T08:19:49","modified_gmt":"2026-09-17T06:19:49","slug":"lufs-true-peak-loudness-meter","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/","title":{"rendered":"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu"},"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;\">Serwisy streamingowe nie odtwarzaj\u0105 utworu tak g\u0142o\u015bno, jak zosta\u0142 zmasterowany, lecz dopasowuj\u0105 go do warto\u015bci docelowej. G\u0142o\u015bny master zostaje \u015bciszony, cichy podg\u0142o\u015bniony tylko na tyle, na ile pozwala poziom szczytowy. Miernik wyznacza warto\u015bci, na kt\u00f3rych opiera si\u0119 ta decyzja, i przelicza normalizacj\u0119 dla Spotify oraz wed\u0142ug zalece\u0144 AES i EBU.<\/p>\n<div id=\"lufs-zone\" class=\"lufs-zone\">\n<p style=\"margin: 0 0 12px; color: var(--text-primary, #e8e8ee); font-weight: 700;\">Pole na plik audio (przeci\u0105ganie i upuszczanie)<\/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;\">Wyb\u00f3r pliku audio<\/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 i AIFF s\u0105 odczytywane pr\u00f3bka po pr\u00f3bce, FLAC, MP3, AAC, Ogg i Opus przez dekoder przegl\u0105darki.<\/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> Pomiar mono jako dual mono (jak przy odtwarzaniu z dw\u00f3ch g\u0142o\u015bnik\u00f3w)<\/label><br \/>\n        <label style=\"display: inline-flex; gap: 8px; align-items: center;\">W\u0142asna warto\u015b\u0107 docelowa <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\">Co oznaczaj\u0105 warto\u015bci<\/div>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">G\u0142o\u015bno\u015b\u0107 zintegrowana: \u015brednia z ca\u0142ego pliku, wa\u017cona krzyw\u0105 K i bramkowana dwoma progami (bezwzgl\u0119dnym \u221270 LUFS, wzgl\u0119dnym 10 LU poni\u017cej \u015bredniej). Na tej warto\u015bci opiera si\u0119 normalizacja.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">True peak: najwy\u017cszy poziom sygna\u0142u mi\u0119dzy pr\u00f3bkami, wyznaczony przez czterokrotne nadpr\u00f3bkowanie. Cz\u0119sto le\u017cy powy\u017cej sample peak.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">Zakres g\u0142o\u015bno\u015bci (LRA): odst\u0119p mi\u0119dzy 10. a 95. percentylem warto\u015bci short-term wed\u0142ug EBU Tech 3342, miara dynamiki w czasie.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">Momentary i short-term: g\u0142o\u015bno\u015b\u0107 w przesuwnych oknach 0,4 i 3 sekund, wyszukiwana tu w siatce 10 ms.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 0 0 6px;\">PLR: odst\u0119p mi\u0119dzy true peak a g\u0142o\u015bno\u015bci\u0105 zintegrowan\u0105. Ma\u0142a warto\u015b\u0107 wskazuje na mocno skompresowany materia\u0142.<\/p>\n<p class=\"lufs-klein\" style=\"margin: 20px 0 0;\">\u0179r\u00f3d\u0142a:<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;\">Plik jest odczytywany i mierzony w przegl\u0105darce i nie opuszcza urz\u0105dzenia. Pomiar wed\u0142ug ITU-R BS.1770-5 (wa\u017cenie K, bramkowanie, czterokrotne nadpr\u00f3bkowanie dla true peak) oraz EBU Tech 3341 i 3342.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Serwisy streamingowe nie odtwarzaj\u0105 utworu tak g\u0142o\u015bno, jak zosta\u0142 zmasterowany, lecz dopasowuj\u0105 go do warto\u015bci docelowej. G\u0142o\u015bny master zostaje \u015bciszony, cichy podg\u0142o\u015bniony tylko na tyle, na ile pozwala poziom szczytowy. Miernik wyznacza warto\u015bci, na kt\u00f3rych opiera si\u0119 ta decyzja, i przelicza normalizacj\u0119 dla Spotify oraz wed\u0142ug zalece\u0144 AES i EBU.\",\"zone_titel\":\"Pole na plik audio (przeci\u0105ganie i upuszczanie)\",\"knopf_datei\":\"Wyb\u00f3r pliku audio\",\"zone_hinweis\":\"WAV i AIFF s\u0105 odczytywane pr\u00f3bka po pr\u00f3bce, FLAC, MP3, AAC, Ogg i Opus przez dekoder przegl\u0105darki.\",\"dualmono\":\"Pomiar mono jako dual mono (jak przy odtwarzaniu z dw\u00f3ch g\u0142o\u015bnik\u00f3w)\",\"eigenes_ziel\":\"W\u0142asna warto\u015b\u0107 docelowa\",\"grenze\":\"Plik jest odczytywany i mierzony w przegl\u0105darce i nie opuszcza urz\u0105dzenia. Pomiar wed\u0142ug ITU-R BS.1770-5 (wa\u017cenie K, bramkowanie, czterokrotne nadpr\u00f3bkowanie dla true peak) oraz EBU Tech 3341 i 3342.\",\"dezimal\":\",\",\"status_lesen\":\"Odczyt pliku \u2026\",\"status_dekodieren\":\"Dekodowanie d\u017awi\u0119ku \u2026\",\"status_messen\":\"Pomiar: {prozent} %\",\"fehler_dekodieren\":\"Pliku nie uda\u0142o si\u0119 zdekodowa\u0107. Albo jest uszkodzony, albo przegl\u0105darka nie obs\u0142uguje formatu; WAV dzia\u0142a w ka\u017cdej przegl\u0105darce.\",\"fehler_webaudio\":\"Ta przegl\u0105darka nie oferuje Web Audio. Pomiar plik\u00f3w WAV i AIFF dzia\u0142a mimo to.\",\"fehler_speicher\":\"Pami\u0119ci przegl\u0105darki nie wystarczy\u0142o na ten plik. Pomaga kr\u00f3tszy plik albo przegl\u0105darka na komputerze.\",\"fehler_leer\":\"Plik nie zawiera danych audio.\",\"fehler_format\":\"Ten wariant WAV lub AIFF nie jest obs\u0142ugiwany ({detail}).\",\"kanal_mono\":\"mono\",\"kanal_stereo\":\"stereo\",\"kanal_mehr\":\"kana\u0142y: {anzahl}\",\"bit\":\"{bits} bit\",\"bit_float\":\"{bits} bit zmiennoprzecinkowo\",\"k_integriert\":\"G\u0142o\u015bno\u015b\u0107 zintegrowana\",\"k_truepeak\":\"True peak (maks.)\",\"k_lra\":\"Zakres g\u0142o\u015bno\u015bci (LRA)\",\"k_mmax\":\"Momentary maks.\",\"k_smax\":\"Short-term maks.\",\"k_samplepeak\":\"Sample peak (maks.)\",\"k_plr\":\"Peak to loudness (PLR)\",\"h_befunde\":\"Ustalenia\",\"h_normalisierung\":\"Normalizacja przy odtwarzaniu\",\"h_verlauf\":\"Przebieg g\u0142o\u015bno\u015bci\",\"h_erklaerung\":\"Co oznaczaj\u0105 warto\u015bci\",\"befund_tp_ueber_null\":\"True peak powy\u017cej 0 dBTP ({tp} dBTP) || Mi\u0119dzy pr\u00f3bkami sygna\u0142 przekracza pe\u0142n\u0105 skal\u0119. Przy konwersji na sygna\u0142 analogowy i kodowaniu do AAC lub Ogg Vorbis powstaj\u0105 z tego zniekszta\u0142cenia.\",\"befund_tp_ueber_eins\":\"True peak powy\u017cej \u22121 dBTP ({tp} dBTP) || Spotify, zalecenie AES TD1008 i EBU R 128 podaj\u0105 \u22121 dBTP jako g\u00f3rn\u0105 granic\u0119, bo kodowanie stratne dodatkowo podnosi szczyty.\",\"befund_spotify_zwei\":\"G\u0142o\u015bniej ni\u017c \u221214 LUFS przy true peak powy\u017cej \u22122 dBTP || Dla master\u00f3w g\u0142o\u015bniejszych ni\u017c \u221214 LUFS Spotify zaleca true peak poni\u017cej \u22122 dBTP.\",\"befund_voll\":\"Pr\u00f3bki na poziomie 0 dBFS lub wy\u017cej: {anzahl} || Warto\u015bci na pe\u0142nej skali wskazuj\u0105 na przesterowanie w masterze lub w kodowaniu.\",\"befund_kurz_lra\":\"Kr\u00f3cej ni\u017c 60 sekund || Wed\u0142ug EBU Tech 3341 zakres g\u0142o\u015bno\u015bci w pierwszych 60 sekundach nie jest jeszcze stabilny.\",\"befund_zu_kurz_s\":\"Kr\u00f3cej ni\u017c 3 sekundy || Plik jest za kr\u00f3tki na g\u0142o\u015bno\u015b\u0107 short-term i zakres g\u0142o\u015bno\u015bci.\",\"befund_zu_kurz_i\":\"Kr\u00f3cej ni\u017c 400 milisekund || Plik jest za kr\u00f3tki na g\u0142o\u015bno\u015b\u0107 zintegrowan\u0105.\",\"befund_still\":\"Brak mierzalnej g\u0142o\u015bno\u015bci || Wszystkie bloki pomiarowe le\u017c\u0105 poni\u017cej progu bezwzgl\u0119dnego \u221270 LUFS.\",\"befund_dualmono\":\"Mono zmierzone jako dual mono || Kana\u0142 liczy si\u0119 podw\u00f3jnie, jak przy odtwarzaniu z dw\u00f3ch g\u0142o\u015bnik\u00f3w. Zmierzona jako pojedynczy kana\u0142 g\u0142o\u015bno\u015b\u0107 by\u0142aby o 3,01 LU ni\u017csza.\",\"befund_rate_unbekannt\":\"Brak cz\u0119stotliwo\u015bci pr\u00f3bkowania w nag\u0142\u00f3wku pliku || Przegl\u0105darka zdekodowa\u0142a plik przy {rate} Hz. Na g\u0142o\u015bno\u015b\u0107 ma to znikomy wp\u0142yw; sample peak dotyczy wtedy przeliczonego sygna\u0142u.\",\"befund_gut\":\"Poziom szczytowy z zapasem || True peak {tp} dBTP: w granicach zalece\u0144 Spotify, AES i EBU.\",\"spotify_satz_leiser\":\"W ustawieniu domy\u015blnym Spotify odtwarza ten utw\u00f3r o {db} dB ciszej.\",\"spotify_satz_lauter\":\"W ustawieniu domy\u015blnym Spotify podg\u0142a\u015bnia ten utw\u00f3r o {db} dB.\",\"spotify_satz_gleich\":\"W ustawieniu domy\u015blnym Spotify odtwarza ten utw\u00f3r praktycznie bez zmian.\",\"sp_vorgabe\":\"Ustawienie\",\"sp_ziel\":\"Cel\",\"sp_aenderung\":\"Zmiana poziomu\",\"sp_ergebnis\":\"Wynik\",\"sp_tp\":\"True peak po zmianie\",\"sp_hinweis\":\"Uwaga\",\"ziel_spotify_normal\":\"Spotify, Normalnie\",\"ziel_spotify_laut\":\"Spotify, G\u0142o\u015bno\",\"ziel_spotify_leise\":\"Spotify, Cicho\",\"ziel_aes_titel\":\"AES TD1008, pojedynczy utw\u00f3r\",\"ziel_aes_album\":\"AES TD1008, najg\u0142o\u015bniejszy utw\u00f3r albumu\",\"ziel_ebu\":\"EBU R 128 (radio i telewizja)\",\"ziel_eigen\":\"W\u0142asna warto\u015b\u0107 docelowa\",\"norm_begrenzt\":\"podg\u0142o\u015bnienie ograniczone: 1 dB zapasu poni\u017cej true peak\",\"norm_kein_platz\":\"bez podg\u0142o\u015bnienia, true peak nie zostawia miejsca\",\"norm_limiter\":\"dzia\u0142a limiter przy \u22121 dB\",\"norm_ueber\":\"true peak po zmianie powy\u017cej \u22121 dBTP\",\"apple_hinweis\":\"Apple Music nie podaje warto\u015bci docelowej: wed\u0142ug dokumentu Apple \u201eApple Digital Masters\u201d Sound Check podg\u0142a\u015bnia lub \u015bcisza utwory, a Apple zaleca co najmniej 1 dB zapasu. Spotify normalizuje albumy jako ca\u0142o\u015b\u0107 i AES zaleca to samo; cichy utw\u00f3r z albumu pozostaje wtedy cichy.\",\"verlauf_hinweis\":\"G\u0142o\u015bno\u015b\u0107 short-term (3 s) w siatce 100 ms. Linia przerywana: g\u0142o\u015bno\u015b\u0107 zintegrowana, kropkowana: w\u0142asna warto\u015b\u0107 docelowa.\",\"erkl_integriert\":\"G\u0142o\u015bno\u015b\u0107 zintegrowana: \u015brednia z ca\u0142ego pliku, wa\u017cona krzyw\u0105 K i bramkowana dwoma progami (bezwzgl\u0119dnym \u221270 LUFS, wzgl\u0119dnym 10 LU poni\u017cej \u015bredniej). Na tej warto\u015bci opiera si\u0119 normalizacja.\",\"erkl_truepeak\":\"True peak: najwy\u017cszy poziom sygna\u0142u mi\u0119dzy pr\u00f3bkami, wyznaczony przez czterokrotne nadpr\u00f3bkowanie. Cz\u0119sto le\u017cy powy\u017cej sample peak.\",\"erkl_lra\":\"Zakres g\u0142o\u015bno\u015bci (LRA): odst\u0119p mi\u0119dzy 10. a 95. percentylem warto\u015bci short-term wed\u0142ug EBU Tech 3342, miara dynamiki w czasie.\",\"erkl_momentan\":\"Momentary i short-term: g\u0142o\u015bno\u015b\u0107 w przesuwnych oknach 0,4 i 3 sekund, wyszukiwana tu w siatce 10 ms.\",\"erkl_plr\":\"PLR: odst\u0119p mi\u0119dzy true peak a g\u0142o\u015bno\u015bci\u0105 zintegrowan\u0105. Ma\u0142a warto\u015b\u0107 wskazuje na mocno skompresowany materia\u0142.\",\"knopf_kopieren\":\"Kopiowanie wynik\u00f3w\",\"kopiert\":\"Skopiowano do schowka\",\"quellen_titel\":\"\u0179r\u00f3d\u0142a\"};<\/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>Mierzy g\u0142o\u015bno\u015b\u0107 zintegrowan\u0105 (LUFS), true peak (dBTP) i zakres g\u0142o\u015bno\u015bci pliku audio bezpo\u015brednio w przegl\u0105darce wed\u0142ug ITU-R BS.1770-5 i EBU R 128 oraz przelicza, jak znormalizowa\u0142yby go Spotify i zalecenie AES. Plik nie opuszcza urz\u0105dzenia.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":9284,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92906],"class_list":["post-18605","page","type-page","status-publish","hentry","tag-music-production-pl"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu - 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\/pl\/narzedzia\/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=\"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Mierzy g\u0142o\u015bno\u015b\u0107 zintegrowan\u0105 (LUFS), true peak (dBTP) i zakres g\u0142o\u015bno\u015bci pliku audio bezpo\u015brednio w przegl\u0105darce wed\u0142ug ITU-R BS.1770-5 i EBU R 128 oraz przelicza, jak znormalizowa\u0142yby go Spotify i zalecenie AES. Plik nie opuszcza urz\u0105dzenia.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/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:49+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\\\/pl\\\/narzedzia\\\/lufs-true-peak-loudness-meter\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/lufs-true-peak-loudness-meter\\\/\",\"name\":\"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-17T06:15:04+00:00\",\"dateModified\":\"2026-09-17T06:19:49+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/lufs-true-peak-loudness-meter\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/lufs-true-peak-loudness-meter\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/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\\\/toolbox-3\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu\"}]},{\"@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":"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu - 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\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/","og_locale":"en_US","og_type":"article","og_title":"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu - Lukas Wojcik - Blog","og_description":"Mierzy g\u0142o\u015bno\u015b\u0107 zintegrowan\u0105 (LUFS), true peak (dBTP) i zakres g\u0142o\u015bno\u015bci pliku audio bezpo\u015brednio w przegl\u0105darce wed\u0142ug ITU-R BS.1770-5 i EBU R 128 oraz przelicza, jak znormalizowa\u0142yby go Spotify i zalecenie AES. Plik nie opuszcza urz\u0105dzenia.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-09-17T06:19:49+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\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/","url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/","name":"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-17T06:15:04+00:00","dateModified":"2026-09-17T06:19:49+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/lufs-true-peak-loudness-meter\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/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\/toolbox-3\/"},{"@type":"ListItem","position":3,"name":"Miernik LUFS i True Peak: g\u0142o\u015bno\u015b\u0107, zakres g\u0142o\u015bno\u015bci i normalizacja w streamingu"}]},{"@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\/18605","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=18605"}],"version-history":[{"count":1,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/18605\/revisions"}],"predecessor-version":[{"id":18608,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/18605\/revisions\/18608"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9284"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=18605"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=18605"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}