{"id":13,"date":"2026-10-11T18:27:42","date_gmt":"2026-10-11T18:27:42","guid":{"rendered":"https:\/\/audio.anderskisling.dk\/?page_id=13"},"modified":"2026-10-11T20:43:39","modified_gmt":"2026-10-11T18:43:39","slug":"waveform","status":"publish","type":"page","link":"https:\/\/audio.anderskisling.dk\/","title":{"rendered":"waveform"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"13\" class=\"elementor elementor-13\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3578d0a e-con-full e-flex e-con e-parent\" data-id=\"3578d0a\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8464f5a elementor-widget elementor-widget-html\" data-id=\"8464f5a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<div class=\"audio-editor-widget aew-blank\">\n<style>\n.audio-editor-widget{--bg:#0b0c10;--panel:#12141b;--line:#232734;--txt:#e8eaf0;--mut:#8089a0;--acc:#00e5ff;--env:#ffd166;\n  box-sizing:border-box;width:100%;max-width:1100px;margin:0 auto;padding:24px;background:var(--bg);color:var(--txt);\n  border:1px solid var(--line);border-radius:16px;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Inter,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.45;text-align:left;position:relative}\n.audio-editor-widget *{box-sizing:border-box}\n.audio-editor-widget [hidden]{display:none!important}\n.audio-editor-widget h3,.audio-editor-widget h4,.audio-editor-widget p{margin:0;padding:0;color:var(--txt);text-transform:none;letter-spacing:0}\n.audio-editor-widget .aew-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}\n.audio-editor-widget .aew-title{display:flex;align-items:center;gap:10px;font-size:16px;font-weight:600;line-height:1.2}\n.audio-editor-widget .aew-dot{width:10px;height:10px;border-radius:50%;background:var(--acc);box-shadow:0 0 12px var(--acc)}\n.audio-editor-widget .aew-badge{font-size:11px;color:var(--acc);border:1px solid rgba(0,229,255,.35);padding:3px 9px;border-radius:99px;background:rgba(0,229,255,.06);white-space:nowrap}\n.audio-editor-widget .aew-drop{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:26px 16px;border:1.5px dashed #2c3244;border-radius:12px;background:var(--panel);cursor:pointer;text-align:center;transition:border-color .2s,background .2s,box-shadow .2s}\n.audio-editor-widget .aew-drop:hover,.audio-editor-widget .aew-drop:focus-within,.audio-editor-widget .aew-drop.aew-over{border-color:var(--acc);background:#0f1a20;box-shadow:0 0 0 4px rgba(0,229,255,.08)}\n.audio-editor-widget .aew-drop.aew-has{padding:12px 16px;flex-direction:row;gap:10px}\n.audio-editor-widget .aew-drop svg{width:26px;height:26px;stroke:var(--acc);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}\n.audio-editor-widget .aew-drop strong{font-weight:600;color:var(--txt)}\n.audio-editor-widget .aew-drop span{color:var(--mut);font-size:12.5px}\n.audio-editor-widget .aew-file{position:absolute;left:0;top:0;width:100%;height:100%;margin:0;padding:0;border:0;opacity:0;cursor:pointer;z-index:2;font-size:0}\n.audio-editor-widget .aew-work{margin-top:16px}\n.audio-editor-widget.aew-blank .aew-work{opacity:.45;pointer-events:none}\n.audio-editor-widget .aew-tools{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:10px}\n.audio-editor-widget .aew-grp{display:flex;gap:6px;flex-wrap:wrap}\n.audio-editor-widget .aew-stage{border:1px solid var(--line);border-radius:12px;background:#0d0f15;overflow:hidden}\n.audio-editor-widget .aew-wave{position:relative;height:300px;touch-action:pan-y;user-select:none;-webkit-user-select:none;outline:none}\n.audio-editor-widget.aew-env .aew-wave{touch-action:none}\n.audio-editor-widget .aew-wave:focus-visible{box-shadow:inset 0 0 0 2px rgba(0,229,255,.45)}\n.audio-editor-widget .aew-cv,.audio-editor-widget .aew-ov{position:absolute;left:0;top:0;width:100%;height:100%;display:block}\n.audio-editor-widget .aew-ov{pointer-events:none}\n.audio-editor-widget .aew-sh{position:absolute;top:0;bottom:0;background:rgba(6,7,10,.66);pointer-events:none}\n.audio-editor-widget .aew-shl{left:0;width:0}\n.audio-editor-widget .aew-shr{left:100%;width:0}\n.audio-editor-widget .aew-h{position:absolute;top:0;bottom:0;left:0;width:16px;pointer-events:none;display:none}\n.audio-editor-widget .aew-h:before{content:\"\";position:absolute;left:7px;top:0;bottom:0;width:2px;background:var(--acc);box-shadow:0 0 8px rgba(0,229,255,.7)}\n.audio-editor-widget .aew-h:after{content:\"\";position:absolute;top:0;width:11px;height:18px;background:var(--acc)}\n.audio-editor-widget .aew-hs:after{left:7px;border-radius:0 0 5px 0}\n.audio-editor-widget .aew-he:after{left:-2px;border-radius:0 0 0 5px}\n.audio-editor-widget .aew-ph{position:absolute;top:0;bottom:0;left:0;width:2px;background:#fff;box-shadow:0 0 6px rgba(255,255,255,.9),0 0 12px rgba(0,229,255,.8);pointer-events:none;display:none}\n.audio-editor-widget .aew-empty{position:absolute;left:0;top:0;right:0;bottom:0;display:none;align-items:center;justify-content:center;color:var(--mut);font-size:13px;pointer-events:none}\n.audio-editor-widget.aew-blank .aew-empty{display:flex}\n.audio-editor-widget .aew-scroll{display:block;margin:0;padding:0 12px 6px}\n.audio-editor-widget .aew-help{padding:8px 14px 10px;color:var(--mut);font-size:12px;border-top:1px solid var(--line)}\n.audio-editor-widget .aew-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:12px 0;padding:10px 14px;background:var(--panel);border:1px solid var(--line);border-radius:12px}\n.audio-editor-widget .aew-sp{flex:1 1 auto}\n.audio-editor-widget .aew-time{font-variant-numeric:tabular-nums;font-size:13px;white-space:nowrap;color:var(--txt)}\n.audio-editor-widget .aew-time span{color:var(--mut)}\n.audio-editor-widget .aew-time em{font-style:normal}\n.audio-editor-widget button.aew-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;min-width:0;margin:0;padding:0 12px;border:1px solid #2c3244;border-radius:8px;background:#171a23;color:var(--txt);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-transform:none;letter-spacing:0;text-decoration:none;box-shadow:none;cursor:pointer;transition:border-color .15s,background .15s,transform .15s,box-shadow .2s}\n.audio-editor-widget button.aew-btn:hover,.audio-editor-widget button.aew-btn:focus,.audio-editor-widget button.aew-btn:active{background:#1d2230;color:var(--txt);border-color:var(--acc);box-shadow:none;outline:none}\n.audio-editor-widget button.aew-btn:disabled{opacity:.4;cursor:not-allowed}\n.audio-editor-widget button.aew-btn.aew-on,.audio-editor-widget button.aew-btn.aew-on:hover,.audio-editor-widget button.aew-btn.aew-on:focus{background:rgba(0,229,255,.14);border-color:var(--acc);color:var(--acc)}\n.audio-editor-widget button.aew-btn.aew-pri,.audio-editor-widget button.aew-btn.aew-pri:hover,.audio-editor-widget button.aew-btn.aew-pri:focus{background:var(--acc);border-color:var(--acc);color:#001318;font-weight:600}\n.audio-editor-widget button.aew-btn.aew-pri:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 6px 24px rgba(0,229,255,.35)}\n.audio-editor-widget button.aew-btn.aew-big{height:42px;padding:0 22px;font-size:14px;border-radius:10px;width:100%}\n.audio-editor-widget button.aew-play{flex:none;width:40px;height:40px;min-width:0;margin:0;padding:0;border:0;border-radius:50%;background:var(--acc);color:#001318;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:none;transition:transform .15s,box-shadow .2s}\n.audio-editor-widget button.aew-play:hover,.audio-editor-widget button.aew-play:focus{background:var(--acc);color:#001318;transform:scale(1.07);box-shadow:0 4px 18px rgba(0,229,255,.4);outline:none}\n.audio-editor-widget button.aew-play svg{width:18px;height:18px;fill:currentColor;stroke:none}\n.audio-editor-widget button.aew-play .aew-ipa{display:none}\n.audio-editor-widget button.aew-play.aew-playing .aew-ip{display:none}\n.audio-editor-widget button.aew-play.aew-playing .aew-ipa{display:block}\n.audio-editor-widget .aew-trim{display:flex;align-items:flex-end;gap:14px;flex-wrap:wrap;margin-bottom:16px}\n.audio-editor-widget .aew-fld{display:flex;flex-direction:column;gap:6px;margin:0}\n.audio-editor-widget .aew-fld>span{font-size:12px;color:var(--mut);text-transform:uppercase;letter-spacing:.6px}\n.audio-editor-widget .aew-fld b{display:flex;align-items:center;height:34px;font-variant-numeric:tabular-nums;font-weight:600}\n.audio-editor-widget input.aew-ti{width:120px;height:34px;margin:0;padding:0 10px;border:1px solid #2c3244;border-radius:8px;background:#0b0c10;color:var(--txt);font-family:inherit;font-size:14px;font-variant-numeric:tabular-nums;text-align:center;outline:none;box-shadow:none}\n.audio-editor-widget input.aew-ti:focus{border-color:var(--acc)}\n.audio-editor-widget .aew-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:16px}\n.audio-editor-widget .aew-card{display:flex;flex-direction:column;gap:14px;padding:18px;background:var(--panel);border:1px solid var(--line);border-radius:12px}\n.audio-editor-widget .aew-ecard{position:relative}\n.audio-editor-widget .aew-card h4{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--acc)}\n.audio-editor-widget .aew-ctl{display:flex;flex-direction:column;gap:8px;margin:0}\n.audio-editor-widget .aew-lab{display:flex;justify-content:space-between;font-size:12px;color:var(--mut);text-transform:uppercase;letter-spacing:.6px}\n.audio-editor-widget .aew-lab b{color:var(--txt);font-weight:600;text-transform:none;letter-spacing:0;font-variant-numeric:tabular-nums}\n.audio-editor-widget input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:18px;margin:0;padding:0;background:transparent;cursor:pointer;border:0;box-shadow:none}\n.audio-editor-widget input[type=range]::-webkit-slider-runnable-track{height:4px;border-radius:4px;background:linear-gradient(90deg,var(--acc) var(--p,50%),#2a3040 var(--p,50%))}\n.audio-editor-widget input[type=range]::-moz-range-track{height:4px;border-radius:4px;background:#2a3040}\n.audio-editor-widget input[type=range]::-moz-range-progress{height:4px;border-radius:4px;background:var(--acc)}\n.audio-editor-widget input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;margin-top:-6px;border-radius:50%;background:#fff;border:3px solid var(--acc);box-shadow:0 0 10px rgba(0,229,255,.5)}\n.audio-editor-widget input[type=range]::-moz-range-thumb{width:10px;height:10px;border-radius:50%;background:#fff;border:3px solid var(--acc)}\n.audio-editor-widget select{width:100%;height:36px;margin:0;padding:0 10px;border-radius:8px;border:1px solid #2c3244;background:#0b0c10;color:var(--txt);font-family:inherit;font-size:14px;outline:none;cursor:pointer;box-shadow:none}\n.audio-editor-widget select:focus{border-color:var(--acc)}\n.audio-editor-widget .aew-sw{position:relative;display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:30px;margin:0;cursor:pointer;color:var(--txt);font-size:13px}\n.audio-editor-widget .aew-sw.aew-inl{justify-content:flex-start}\n.audio-editor-widget .aew-sw input{position:absolute;opacity:0;width:0;height:0}\n.audio-editor-widget .aew-sw i{position:relative;width:42px;height:24px;border-radius:99px;background:#2a3040;transition:background .2s;flex:none;order:2}\n.audio-editor-widget .aew-sw.aew-inl i{order:0}\n.audio-editor-widget .aew-sw i:after{content:\"\";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:transform .2s}\n.audio-editor-widget .aew-sw input:checked+i{background:var(--acc)}\n.audio-editor-widget .aew-sw input:checked+i:after{transform:translateX(18px)}\n.audio-editor-widget .aew-sw input:focus-visible+i{box-shadow:0 0 0 3px rgba(0,229,255,.3)}\n.audio-editor-widget .aew-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\n.audio-editor-widget .aew-stats.aew-two{grid-template-columns:repeat(2,1fr)}\n.audio-editor-widget .aew-stat{padding:9px 10px;background:#0b0c10;border:1px solid var(--line);border-radius:8px;min-width:0}\n.audio-editor-widget .aew-stat span{display:block;font-size:11px;color:var(--mut);text-transform:uppercase;letter-spacing:.6px}\n.audio-editor-widget .aew-stat b{display:block;font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.audio-editor-widget .aew-note{font-size:12.5px;color:var(--mut)}\n.audio-editor-widget .aew-note.aew-sm{font-size:11.5px}\n.audio-editor-widget .aew-status{margin-top:10px;color:var(--mut);font-size:12.5px;min-height:18px}\n.audio-editor-widget .aew-status.aew-err{color:#ff6b81}\n.audio-editor-widget .aew-dlg{position:absolute;left:0;top:0;right:0;bottom:0;z-index:5;display:flex;align-items:center;justify-content:center;padding:16px;background:rgba(11,12,16,.96);border-radius:12px}\n.audio-editor-widget .aew-dlgin{display:flex;flex-direction:column;gap:12px;width:100%}\n.audio-editor-widget .aew-nm{display:flex;align-items:center;gap:6px}\n.audio-editor-widget input.aew-ti.aew-fname{flex:1;width:100%;min-width:0;text-align:left}\n.audio-editor-widget .aew-nm b{height:auto;color:var(--mut);font-weight:500}\n@media(max-width:560px){.audio-editor-widget{padding:14px}.audio-editor-widget .aew-wave{height:230px}.audio-editor-widget .aew-stats{grid-template-columns:1fr}.audio-editor-widget .aew-stats.aew-two{grid-template-columns:1fr 1fr}}\n<\/style>\n\n<div class=\"aew-head\">\n  <h3 class=\"aew-title\"><span class=\"aew-dot\"><\/span>Audio Editor<\/h3>\n  <span class=\"aew-badge\">100% lokalt \u00b7 ingen upload<\/span>\n<\/div>\n\n<div class=\"aew-drop\">\n  <svg viewBox=\"0 0 24 24\"><path d=\"M12 16V4M7 9l5-5 5 5M4 16v3a1 1 0 001 1h14a1 1 0 001-1v-3\"\/><\/svg>\n  <div><strong class=\"aew-dn\">Tr\u00e6k en lydfil hertil<\/strong><br><span class=\"aew-ds\">MP3, WAV eller M4A \u2013 eller klik for at v\u00e6lge<\/span><\/div>\n  <input class=\"aew-file\" type=\"file\" accept=\"audio\/*,.mp3,.wav,.m4a,.aac,.ogg,.flac\" aria-label=\"V\u00e6lg eller tr\u00e6k en lydfil hertil\" title=\"V\u00e6lg lydfil\">\n<\/div>\n\n<div class=\"aew-status\" role=\"status\" aria-live=\"polite\">Klar \u2013 slip en lydfil for at begynde.<\/div>\n\n<div class=\"aew-work\">\n  <div class=\"aew-tools\">\n    <div class=\"aew-grp\">\n      <button type=\"button\" class=\"aew-btn aew-zout\" title=\"Zoom ud\">\u2212<\/button>\n      <button type=\"button\" class=\"aew-btn aew-zin\" title=\"Zoom ind\">+<\/button>\n      <button type=\"button\" class=\"aew-btn aew-zfit\" title=\"Vis hele filen\">Fit<\/button>\n      <button type=\"button\" class=\"aew-btn aew-zsel\" title=\"Zoom til det trimmede omr\u00e5de\">Zoom til markering<\/button>\n    <\/div>\n    <div class=\"aew-grp\">\n      <button type=\"button\" class=\"aew-btn aew-toolsel aew-on\" title=\"Mark\u00f8r: afspilning, trim og fades\">Mark\u00f8r<\/button>\n      <button type=\"button\" class=\"aew-btn aew-toolenv\" title=\"Envelope-v\u00e6rkt\u00f8j (E)\">Envelope<\/button>\n    <\/div>\n  <\/div>\n  <div class=\"aew-stage\">\n    <div class=\"aew-wave\" tabindex=\"0\" aria-label=\"Lydb\u00f8lge. Mellemrum afspiller, I og O s\u00e6tter start og slut, E skifter til envelope-v\u00e6rkt\u00f8j.\">\n      <canvas class=\"aew-cv\"><\/canvas>\n      <canvas class=\"aew-ov\"><\/canvas>\n      <div class=\"aew-sh aew-shl\"><\/div><div class=\"aew-sh aew-shr\"><\/div>\n      <div class=\"aew-h aew-hs\"><\/div><div class=\"aew-h aew-he\"><\/div>\n      <div class=\"aew-ph\"><\/div>\n      <div class=\"aew-empty\">Slip en lydfil ovenfor for at begynde<\/div>\n    <\/div>\n    <input class=\"aew-scroll\" type=\"range\" min=\"0\" max=\"1000\" step=\"1\" value=\"0\" style=\"--p:0%\" hidden aria-label=\"Rul i b\u00f8lgen\">\n    <div class=\"aew-help\"><\/div>\n  <\/div>\n  <div class=\"aew-bar\">\n    <button type=\"button\" class=\"aew-play\" aria-label=\"Afspil\"><svg class=\"aew-ip\" viewBox=\"0 0 24 24\"><path d=\"M7 4.5v15a1 1 0 001.5.86l12-7.5a1 1 0 000-1.72l-12-7.5A1 1 0 007 4.5z\"\/><\/svg><svg class=\"aew-ipa\" viewBox=\"0 0 24 24\"><rect x=\"6\" y=\"4\" width=\"4.2\" height=\"16\" rx=\"1.2\"\/><rect x=\"13.8\" y=\"4\" width=\"4.2\" height=\"16\" rx=\"1.2\"\/><\/svg><\/button>\n    <button type=\"button\" class=\"aew-btn aew-stop\" title=\"Stop og g\u00e5 til start af markeringen\">\u25a0 Stop<\/button>\n    <label class=\"aew-sw aew-inl\"><input type=\"checkbox\" class=\"aew-loop\"><i><\/i><span>Gentag markering<\/span><\/label>\n    <div class=\"aew-time\"><b class=\"aew-tcur\">0:00.00<\/b> <span>\/ <em class=\"aew-tt\">0:00.00<\/em><\/span><\/div>\n    <div class=\"aew-sp\"><\/div>\n    <button type=\"button\" class=\"aew-btn aew-sets\" title=\"S\u00e6t start ved afspilningsmark\u00f8ren (I)\">S\u00e6t start (I)<\/button>\n    <button type=\"button\" class=\"aew-btn aew-sete\" title=\"S\u00e6t slut ved afspilningsmark\u00f8ren (O)\">S\u00e6t slut (O)<\/button>\n  <\/div>\n  <div class=\"aew-trim\">\n    <label class=\"aew-fld\"><span>Start<\/span><input class=\"aew-ti aew-fs\" type=\"text\" inputmode=\"decimal\" spellcheck=\"false\" value=\"0:00.000\"><\/label>\n    <label class=\"aew-fld\"><span>Slut<\/span><input class=\"aew-ti aew-fe\" type=\"text\" inputmode=\"decimal\" spellcheck=\"false\" value=\"0:00.000\"><\/label>\n    <div class=\"aew-fld\"><span>L\u00e6ngde<\/span><b class=\"aew-len\">0:00.000<\/b><\/div>\n    <button type=\"button\" class=\"aew-btn aew-reset\">Nulstil trim<\/button>\n  <\/div>\n\n  <div class=\"aew-cards\">\n    <section class=\"aew-card\">\n      <h4>Effekter<\/h4>\n      <label class=\"aew-sw\"><span>Reverse (vend lyden)<\/span><input type=\"checkbox\" class=\"aew-rev\"><i><\/i><\/label>\n      <div class=\"aew-ctl\">\n        <span class=\"aew-lab\">Fade<\/span>\n        <div class=\"aew-stats aew-two\">\n          <div class=\"aew-stat\"><span>Fade in<\/span><b class=\"aew-fiv\">0.00 s<\/b><\/div>\n          <div class=\"aew-stat\"><span>Fade out<\/span><b class=\"aew-fov\">0.00 s<\/b><\/div>\n        <\/div>\n        <p class=\"aew-note aew-sm\">Tr\u00e6k i de runde h\u00e5ndtag \u00f8verst p\u00e5 b\u00f8lgen. Fades er line\u00e6re.<\/p>\n        <button type=\"button\" class=\"aew-btn aew-fres\">Nulstil fades<\/button>\n      <\/div>\n      <div class=\"aew-ctl\">\n        <span class=\"aew-lab\">Gain envelope <b class=\"aew-envn\">Ingen punkter<\/b><\/span>\n        <p class=\"aew-note aew-sm\">V\u00e6lg \u201cEnvelope\u201d over b\u00f8lgen. Klik for at tilf\u00f8je et punkt, tr\u00e6k for at flytte, dobbeltklik for at fjerne.<\/p>\n        <div class=\"aew-grp\">\n          <button type=\"button\" class=\"aew-btn aew-envdel\" disabled>Slet valgt punkt<\/button>\n          <button type=\"button\" class=\"aew-btn aew-envclr\" disabled>Ryd envelope<\/button>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <section class=\"aew-card\">\n      <h4>Normalisering<\/h4>\n      <div class=\"aew-ctl\"><span class=\"aew-lab\">Metode<\/span>\n        <select class=\"aew-mode\"><option value=\"off\">Fra<\/option><option value=\"peak\" selected>Peak (dBFS)<\/option><option value=\"lufs\">Loudness (LUFS)<\/option><\/select><\/div>\n      <label class=\"aew-ctl aew-rtp\"><span class=\"aew-lab\">M\u00e5l-peak <b data-o=\"tp\"><\/b><\/span><input class=\"aew-tp\" type=\"range\" min=\"-12\" max=\"0\" step=\"0.1\" value=\"-1\"><\/label>\n      <label class=\"aew-ctl aew-rtl\" hidden><span class=\"aew-lab\">M\u00e5l-loudness <b data-o=\"tl\"><\/b><\/span><input class=\"aew-tl\" type=\"range\" min=\"-30\" max=\"-6\" step=\"0.5\" value=\"-14\"><\/label>\n      <label class=\"aew-ctl aew-rtc\" hidden><span class=\"aew-lab\">Peak-loft <b data-o=\"tc\"><\/b><\/span><input class=\"aew-tcl\" type=\"range\" min=\"-6\" max=\"0\" step=\"0.1\" value=\"-1\"><\/label>\n      <div class=\"aew-stats\">\n        <div class=\"aew-stat\"><span>Peak<\/span><b class=\"aew-sp1\">\u2013<\/b><\/div>\n        <div class=\"aew-stat\"><span>Loudness<\/span><b class=\"aew-sl1\">\u2013<\/b><\/div>\n        <div class=\"aew-stat\"><span>Gain<\/span><b class=\"aew-sg1\">\u2013<\/b><\/div>\n      <\/div>\n      <p class=\"aew-note aew-nn\"><\/p>\n      <label class=\"aew-sw\"><span>H\u00f8r \/ vis med normalisering<\/span><input type=\"checkbox\" class=\"aew-prev\" checked><i><\/i><\/label>\n      <p class=\"aew-note aew-sm\">M\u00e5les p\u00e5 det trimmede omr\u00e5de, f\u00f8r envelope og fades. Eksporten bruger altid den valgte normalisering.<\/p>\n    <\/section>\n\n    <section class=\"aew-card aew-ecard\">\n      <h4>Eksport\u00e9r lyd<\/h4>\n      <div class=\"aew-ctl\"><span class=\"aew-lab\">Format<\/span>\n        <select class=\"aew-fmt\"><option value=\"wav16\">WAV \u00b7 16-bit<\/option><option value=\"wav24\">WAV \u00b7 24-bit<\/option><option value=\"mp3\">MP3<\/option><\/select><\/div>\n      <div class=\"aew-ctl aew-rk\" hidden><span class=\"aew-lab\">Bitrate<\/span>\n        <select class=\"aew-kbps\"><option value=\"128\">128 kbps<\/option><option value=\"160\">160 kbps<\/option><option value=\"192\" selected>192 kbps<\/option><option value=\"256\">256 kbps<\/option><option value=\"320\">320 kbps<\/option><\/select><\/div>\n      <p class=\"aew-note aew-out\">\u2013<\/p>\n      <button type=\"button\" class=\"aew-btn aew-pri aew-big aew-exp\">Gem lyd \u2026<\/button>\n      <p class=\"aew-note aew-sm\">Det trimmede omr\u00e5de gemmes med reverse, normalisering, envelope og fades p\u00e5f\u00f8rt. Du v\u00e6lger selv sted og filnavn. MP3-encoderen hentes f\u00f8rst, f\u00f8rste gang du bruger MP3.<\/p>\n      <div class=\"aew-dlg\" hidden role=\"dialog\" aria-modal=\"true\" aria-label=\"Gem som\">\n        <div class=\"aew-dlgin\">\n          <h4>Gem som<\/h4>\n          <div class=\"aew-fld\"><span>Filnavn<\/span><div class=\"aew-nm\"><input class=\"aew-ti aew-fname\" type=\"text\" spellcheck=\"false\" autocomplete=\"off\" aria-label=\"Filnavn\"><b class=\"aew-ext\">.wav<\/b><\/div><\/div>\n          <p class=\"aew-note aew-sm\">Din browser kan ikke \u00e5bne et arkivvalg her, s\u00e5 filen l\u00e6gges i din download-mappe. Sl\u00e5 \u201cSp\u00f8rg, hvor filer skal gemmes\u201d til i browserens indstillinger, hvis du selv vil v\u00e6lge mappe.<\/p>\n          <div class=\"aew-grp\"><button type=\"button\" class=\"aew-btn aew-dcancel\">Annull\u00e9r<\/button><button type=\"button\" class=\"aew-btn aew-pri aew-dok\">Gem<\/button><\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\/* MP3-encoder (lamejs) hentes kun ved MP3-eksport. L\u00e6g evt. din egen kopi i WordPress' mediebibliotek og s\u00e6t dens URL f\u00f8rst i listen. *\/\nvar LAME_SOURCES=['https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/lamejs\/1.2.1\/lame.min.js','https:\/\/cdn.jsdelivr.net\/npm\/lamejs@1.2.1\/lame.min.js'];\nvar lamePromise=null;\nfunction lameReady(){return !!(window.lamejs&&window.lamejs.Mp3Encoder);}\nfunction loadLame(){\n  if(lameReady())return Promise.resolve(window.lamejs);\n  if(lamePromise)return lamePromise;\n  lamePromise=new Promise(function(res,rej){\n    var i=0;\n    (function next(){\n      if(i>=LAME_SOURCES.length){lamePromise=null;rej(new Error('Kunne ikke hente MP3-encoderen. Eksport\u00e9r som WAV, eller peg LAME_SOURCES p\u00e5 en selvhostet lame.min.js.'));return;}\n      var s=document.createElement('script');s.src=LAME_SOURCES[i++];s.async=true;\n      s.onload=function(){lameReady()?res(window.lamejs):next();};\n      s.onerror=function(){if(s.parentNode)s.parentNode.removeChild(s);next();};\n      document.head.appendChild(s);\n    })();\n  });\n  return lamePromise;\n}\nfunction tick(){return new Promise(function(r){setTimeout(r,0)});}\nfunction fmt(t,d){\n  if(!(t>0))t=0;\n  var ms=Math.round(t*1000),m=Math.floor(ms\/60000),s=Math.floor(ms%60000\/1000),r=ms%1000,o=m+':'+('0'+s).slice(-2);\n  if(d>0)o+='.'+('00'+r).slice(-3).slice(0,d);\n  return o;\n}\nfunction parseT(v){\n  v=String(v).trim().replace(',','.');if(!v)return null;\n  var p=v.split(':'),t=0,i;if(p.length>3)return null;\n  for(i=0;i<p.length;i++){if(!\/^(\\d+(\\.\\d*)?|\\.\\d+)$\/.test(p[i]))return null;t=t*60+parseFloat(p[i]);}\n  return t;\n}\nfunction kcoef(fs){\n  var f0=1681.9744509555319,G=3.999843853973347,Q=0.7071752369554196,K=Math.tan(Math.PI*f0\/fs),Vh=Math.pow(10,G\/20),Vb=Math.pow(Vh,0.4996667741545416),a0=1+K\/Q+K*K;\n  var s1={b0:(Vh+Vb*K\/Q+K*K)\/a0,b1:2*(K*K-Vh)\/a0,b2:(Vh-Vb*K\/Q+K*K)\/a0,a1:2*(K*K-1)\/a0,a2:(1-K\/Q+K*K)\/a0};\n  f0=38.13547087613982;Q=0.5003270373253953;K=Math.tan(Math.PI*f0\/fs);a0=1+K\/Q+K*K;\n  var s2={a1:2*(K*K-1)\/a0,a2:(1-K\/Q+K*K)\/a0};\n  return [s1,s2];\n}\nfunction init(root){\n  if(root.getAttribute('data-ae'))return;root.setAttribute('data-ae','1');\n  var $=function(s){return root.querySelector(s)},$$=function(s){return root.querySelectorAll(s)};\n  var el={drop:$('.aew-drop'),file:$('.aew-file'),work:$('.aew-work'),cv:$('.aew-cv'),ov:$('.aew-ov'),wave:$('.aew-wave'),shl:$('.aew-shl'),shr:$('.aew-shr'),hs:$('.aew-hs'),he:$('.aew-he'),ph:$('.aew-ph'),scroll:$('.aew-scroll'),help:$('.aew-help'),\n    tcur:$('.aew-tcur'),tt:$('.aew-tt'),play:$('.aew-play'),stop:$('.aew-stop'),loop:$('.aew-loop'),sets:$('.aew-sets'),sete:$('.aew-sete'),fs:$('.aew-fs'),fe:$('.aew-fe'),len:$('.aew-len'),reset:$('.aew-reset'),\n    zin:$('.aew-zin'),zout:$('.aew-zout'),zfit:$('.aew-zfit'),zsel:$('.aew-zsel'),toolSel:$('.aew-toolsel'),toolEnv:$('.aew-toolenv'),\n    rev:$('.aew-rev'),fiv:$('.aew-fiv'),fov:$('.aew-fov'),fres:$('.aew-fres'),envn:$('.aew-envn'),envdel:$('.aew-envdel'),envclr:$('.aew-envclr'),\n    mode:$('.aew-mode'),tp:$('.aew-tp'),tl:$('.aew-tl'),tcl:$('.aew-tcl'),rtp:$('.aew-rtp'),rtl:$('.aew-rtl'),rtc:$('.aew-rtc'),\n    prev:$('.aew-prev'),sPeak:$('.aew-sp1'),sLufs:$('.aew-sl1'),sGain:$('.aew-sg1'),note:$('.aew-nn'),fmt:$('.aew-fmt'),kbps:$('.aew-kbps'),rk:$('.aew-rk'),out:$('.aew-out'),exp:$('.aew-exp'),status:$('.aew-status'),\n    dlg:$('.aew-dlg'),dlgName:$('.aew-fname'),dlgExt:$('.aew-ext'),dlgOk:$('.aew-dok'),dlgCancel:$('.aew-dcancel')};\n  var BIN=256,MIN=0.02,RUL=22,HYO=14,DBMIN=-48,DBMAX=12,FXWIN=20,FXRATE=100;\n  var STEPS=[0.001,0.002,0.005,0.01,0.02,0.05,0.1,0.2,0.5,1,2,5,10,15,30,60,120,300,600,900,1800,3600];\n  var buf=null,dur=0,sr=44100,chN=2,ctx=null,gainNode=null,fxNode=null,bins=null,kw=null,fileName='lyd',loadId=0,busy=false;\n  var trimS=0,trimE=0,view={s:0,sp:1},pos=0,playing=false,src=null,t0=0,p0=0,raf=0,drag=null,wasPlaying=false,mt=0;\n  var meas={peak:0,lufs:null},gainInfo={g:1,limited:false};\n  var fadeIn=0,fadeOut=0,env=[],selPt=null,dragPt=null,tool='sel',selA=0,selX=0,selMoved=false;\n  var revBuf=null,revKey='',fxEnd=0,dW=false,dO=false,drawRaf=0,dlgRes=null,dlgFm=null;\n  el.work.inert=true;\n  function say(t,err){el.status.textContent=t;el.status.classList.toggle('aew-err',!!err);}\n  function dbTxt(v){return v>1e-9?(20*Math.log10(v)).toFixed(1):'\u2212\u221e';}\n  function W(){return el.wave.clientWidth||1;}\n  function H(){return el.wave.clientHeight||1;}\n  function X(t){return (t-view.s)\/view.sp*W();}\n  function T(x){return view.s+x\/W()*view.sp;}\n  function clamp(v,a,b){return v<a?a:(v>b?b:v);}\n  \/* ---------- Lyd-graf: kilde \u2192 normalisering \u2192 fx (fades\/envelope) \u2192 ud ---------- *\/\n  function ensureCtx(){\n    if(ctx)return;\n    var AC=window.AudioContext||window.webkitAudioContext;\n    if(!AC)throw new Error('Din browser underst\u00f8tter ikke Web Audio API.');\n    ctx=new AC();gainNode=ctx.createGain();fxNode=ctx.createGain();\n    gainNode.connect(fxNode);fxNode.connect(ctx.destination);\n  }\n  function decode(ab){return new Promise(function(res,rej){var pr=ctx.decodeAudioData(ab,res,rej);if(pr&&pr.then)pr.then(res,rej);});}\n  function readFile(f){return f.arrayBuffer?f.arrayBuffer():new Promise(function(res,rej){var r=new FileReader();r.onload=function(){res(r.result)};r.onerror=rej;r.readAsArrayBuffer(f);});}\n  \/* ---------- Gain-funktion: envelope (line\u00e6r i dB) \u00d7 line\u00e6re fades. S\/L = start og l\u00e6ngde i sekunder ---------- *\/\n  function mkGain(S,L){\n    var pts=env.slice(),n=pts.length,k=0,fi=fadeIn,fo=fadeOut;\n    return function(t){\n      var g=1;\n      if(n){\n        var db;\n        if(t<=pts[0].t)db=pts[0].db;\n        else if(t>=pts[n-1].t)db=pts[n-1].db;\n        else{\n          while(k>0&&pts[k].t>t)k--;\n          while(k<n-2&&pts[k+1].t<=t)k++;\n          var a=pts[k],b=pts[k+1],d=b.t-a.t;\n          db=d>1e-9?a.db+(b.db-a.db)*(t-a.t)\/d:b.db;\n        }\n        g=db<=DBMIN+1e-6?0:Math.exp(db*0.11512925464970229);\n      }\n      var u=t-S;\n      if(fi>0&&u<fi)g*=u<0?0:u\/fi;\n      var r=L-u;\n      if(fo>0&&r<fo)g*=r<0?0:r\/fo;\n      return g;\n    };\n  }\n  function fxActive(){return env.length>0||fadeIn>0||fadeOut>0;}\n  \/* ---------- Analyse ---------- *\/\n  function buildBins(){\n    var len=buf.length,nb=Math.ceil(len\/BIN),mn=new Float32Array(nb),mx=new Float32Array(nb),c,b,i;\n    mn.fill(1);mx.fill(-1);\n    for(c=0;c<chN;c++){\n      var d=buf.getChannelData(c);\n      for(b=0;b<nb;b++){\n        var s=b*BIN,e=Math.min(len,s+BIN),lo=mn[b],hi=mx[b];\n        for(i=s;i<e;i++){var v=d[i];if(v<lo)lo=v;if(v>hi)hi=v;}\n        mn[b]=lo;mx[b]=hi;\n      }\n    }\n    bins={mn:mn,mx:mx,nb:nb};\n  }\n  async function kweight(b){\n    var fs=b.sampleRate,seg=Math.round(fs*0.1),n=Math.floor(b.length\/seg),nc=chN,co=kcoef(fs),A=co[0],B=co[1],ss=new Float64Array(n*nc);\n    var b0=A.b0,b1=A.b1,b2=A.b2,a1=A.a1,a2=A.a2,c1=B.a1,c2=B.a2;\n    for(var c=0;c<nc;c++){\n      var d=b.getChannelData(c),z1=0,z2=0,w1=0,w2=0;\n      for(var s=0;s<n;s++){\n        var acc=0,end=(s+1)*seg;\n        for(var i=s*seg;i<end;i++){\n          var x=d[i],y=b0*x+z1;z1=b1*x-a1*y+z2;z2=b2*x-a2*y;\n          var v=y+w1;w1=-2*y-c1*v+w2;w2=y-c2*v;\n          acc+=v*v;\n        }\n        ss[c*n+s]=acc;\n        if(s%200===199)await tick();\n      }\n    }\n    return {ss:ss,n:n,nc:nc,seg:seg,sr:fs};\n  }\n  function lufsIn(a,b){\n    if(!kw)return null;\n    var n=kw.n,nc=kw.nc,ss=kw.ss,seg=kw.seg,s0=Math.ceil(a*kw.sr\/seg),s1=Math.min(n,Math.floor(b*kw.sr\/seg)),cnt=s1-s0-3;\n    if(cnt<1)return null;\n    var z=new Float64Array(cnt),i,c,sum=0,m=0;\n    for(i=0;i<cnt;i++){var t=0;for(c=0;c<nc;c++){var o=c*n+s0+i;t+=ss[o]+ss[o+1]+ss[o+2]+ss[o+3];}z[i]=t\/(4*seg);}\n    for(i=0;i<cnt;i++)if(z[i]>0&&-0.691+10*Math.log10(z[i])>-70){sum+=z[i];m++;}\n    if(!m)return null;\n    var gr=-0.691+10*Math.log10(sum\/m)-10;sum=0;m=0;\n    for(i=0;i<cnt;i++)if(z[i]>0&&-0.691+10*Math.log10(z[i])>gr){sum+=z[i];m++;}\n    return m?-0.691+10*Math.log10(sum\/m):null;\n  }\n  function peakIn(a,b){\n    var i0=Math.max(0,Math.floor(a*sr)),i1=Math.min(buf.length,Math.ceil(b*sr)),m=0,c,i;\n    for(c=0;c<chN;c++){var d=buf.getChannelData(c);for(i=i0;i<i1;i++){var v=d[i];if(v<0)v=-v;if(v>m)m=v;}}\n    return m;\n  }\n  function scheduleMeasure(){clearTimeout(mt);mt=setTimeout(measure,140);}\n  function measure(){\n    if(!buf)return;\n    meas.peak=peakIn(trimS,trimE);meas.lufs=lufsIn(trimS,trimE);applyGain(true);\n  }\n  \/* ---------- Normalisering ---------- *\/\n  function applyGain(redraw){\n    var m=el.mode.value,g=1,lim=false;\n    if(buf){\n      if(m==='peak'){if(meas.peak>1e-6)g=Math.pow(10,+el.tp.value\/20)\/meas.peak;}\n      else if(m==='lufs'&&meas.lufs!=null){\n        g=Math.pow(10,(+el.tl.value-meas.lufs)\/20);\n        var cl=Math.pow(10,+el.tcl.value\/20);\n        if(meas.peak>1e-6&&meas.peak*g>cl){g=cl\/meas.peak;lim=true;}\n      }\n    }\n    gainInfo={g:g,limited:lim};\n    setStats();\n    if(gainNode)gainNode.gain.setTargetAtTime(el.prev.checked?g:1,ctx.currentTime,0.015);\n    if(redraw)invalidate(true,false);\n  }\n  function setStats(){\n    if(!buf){el.sPeak.textContent=el.sLufs.textContent=el.sGain.textContent='\u2013';el.note.textContent='';return;}\n    var m=el.mode.value,gd=20*Math.log10(gainInfo.g),n;\n    el.sPeak.textContent=dbTxt(meas.peak)+' dBFS';\n    el.sLufs.textContent=!kw?'M\u00e5ler \u2026':(meas.lufs==null?'\u2013':meas.lufs.toFixed(1)+' LUFS');\n    el.sGain.textContent=(gd>=0?'+':'')+gd.toFixed(1)+' dB';\n    if(m==='off')n='Normalisering er sl\u00e5et fra.';\n    else if(m==='lufs'&&!kw)n='M\u00e5ler loudness \u2026';\n    else if(m==='lufs'&&meas.lufs==null)n='Loudness kan ikke m\u00e5les i det valgte omr\u00e5de (for kort eller stille) \u2013 ingen gain anvendt.';\n    else if(m==='peak'&&meas.peak<=1e-6)n='Omr\u00e5det er stille \u2013 ingen gain anvendt.';\n    else if(gainInfo.limited)n='Gain er begr\u00e6nset af peak-loftet, s\u00e5 m\u00e5l-loudness n\u00e5s ikke fuldt ud.';\n    else n='Efter normalisering: peak '+dbTxt(meas.peak*gainInfo.g)+' dBFS'+(m==='lufs'?' \u00b7 loudness '+(meas.lufs+gd).toFixed(1)+' LUFS':'')+'.';\n    el.note.textContent=n;\n  }\n  function sync(){\n    var m=el.mode.value;\n    $('[data-o=tp]').textContent=(+el.tp.value).toFixed(1)+' dBFS';\n    $('[data-o=tl]').textContent=(+el.tl.value).toFixed(1)+' LUFS';\n    $('[data-o=tc]').textContent=(+el.tcl.value).toFixed(1)+' dBFS';\n    [el.tp,el.tl,el.tcl].forEach(function(r){r.style.setProperty('--p',((r.value-r.min)\/(r.max-r.min)*100)+'%');});\n    el.rtp.hidden=m!=='peak';el.rtl.hidden=m!=='lufs';el.rtc.hidden=m!=='lufs';\n    el.rk.hidden=el.fmt.value!=='mp3';\n  }\n  \/* ---------- Envelope \/ fades: hj\u00e6lpefunktioner ---------- *\/\n  function EY0(){return RUL+26;}\n  function EY1(){return H()-8;}\n  function dbToY(db){var a=EY0(),b=EY1();return a+(DBMAX-db)\/(DBMAX-DBMIN)*(b-a);}\n  function yToDb(y){var a=EY0(),b=EY1();return DBMAX-(y-a)\/(b-a)*(DBMAX-DBMIN);}\n  function snapDb(db){db=clamp(db,DBMIN,DBMAX);if(Math.abs(db)<0.8)db=0;else if(db<DBMIN+1)db=DBMIN;return Math.round(db*10)\/10;}\n  function sortEnv(){env.sort(function(a,b){return a.t-b.t;});}\n  function addPt(t,db){var p={t:clamp(t,0,dur),db:snapDb(db)};env.push(p);sortEnv();return p;}\n  function hitPt(x,y){\n    var best=-1,bd=1e9,i;\n    for(i=0;i<env.length;i++){var d=Math.hypot(X(env[i].t)-x,dbToY(env[i].db)-y);if(d<bd){bd=d;best=i;}}\n    return bd<=10?best:-1;\n  }\n  function envUI(){\n    el.envn.textContent=env.length?env.length+(env.length===1?' punkt':' punkter'):'Ingen punkter';\n    el.envclr.disabled=!env.length;el.envdel.disabled=!selPt;\n  }\n  function envChanged(){envUI();scheduleFx();invalidate(true,true);}\n  function delSel(){\n    if(!selPt)return;\n    var i=env.indexOf(selPt);if(i>=0)env.splice(i,1);\n    selPt=null;envChanged();\n  }\n  function clampFades(){var L=trimE-trimS,s=fadeIn+fadeOut;if(s>L&&s>0){var k=L\/s;fadeIn*=k;fadeOut*=k;}}\n  function fadeUI(){el.fiv.textContent=fadeIn.toFixed(2)+' s';el.fov.textContent=fadeOut.toFixed(2)+' s';}\n  function helpTxt(){\n    el.help.textContent=tool==='env'\n      ?'Envelope: klik for at tilf\u00f8je et punkt \u00b7 tr\u00e6k for at flytte \u00b7 dobbeltklik eller Delete for at fjerne \u00b7 klik i tidslinjalen for at flytte afspilningsmark\u00f8ren \u00b7 E = tilbage til mark\u00f8r'\n      :'Tr\u00e6k i start\/slut-h\u00e5ndtagene, eller hold Shift og tr\u00e6k over b\u00f8lgen for at v\u00e6lge et omr\u00e5de \u00b7 tr\u00e6k i de runde fade-h\u00e5ndtag \u00b7 Ctrl + scroll = zoom \u00b7 Mellemrum = afspil \u00b7 E = envelope';\n  }\n  function setTool(t){\n    tool=t;root.classList.toggle('aew-env',t==='env');\n    el.toolSel.classList.toggle('aew-on',t==='sel');el.toolEnv.classList.toggle('aew-on',t==='env');\n    helpTxt();invalidate(false,true);\n  }\n  \/* ---------- Tegning ---------- *\/\n  function invalidate(w,o){\n    if(w)dW=true;if(o)dO=true;\n    if(drawRaf)return;\n    drawRaf=requestAnimationFrame(function(){\n      drawRaf=0;\n      if(dW){dW=false;drawWave();}\n      if(dO){dO=false;drawOv();}\n      layout();\n    });\n  }\n  function drawWave(){\n    var cv=el.cv,w=W(),h=H();\n    if(!w||!h)return;\n    var dpr=Math.min(window.devicePixelRatio||1,2),cw=Math.round(w*dpr),ch=Math.round(h*dpr);\n    if(cv.width!==cw||cv.height!==ch){cv.width=cw;cv.height=ch;}\n    var g=cv.getContext('2d');g.setTransform(1,0,0,1,0,0);g.clearRect(0,0,cw,ch);\n    if(!buf)return;\n    var rul=Math.round(RUL*dpr),mid=rul+(ch-rul)\/2,amp=(ch-rul)\/2*0.92,pps=w\/view.sp,st=STEPS[STEPS.length-1],q;\n    for(q=0;q<STEPS.length;q++){if(STEPS[q]*pps>=90){st=STEPS[q];break;}}\n    g.fillStyle='rgba(255,255,255,.07)';g.fillRect(0,rul-1,cw,1);\n    g.font=(10.5*dpr)+'px -apple-system,Segoe UI,Roboto,sans-serif';g.textBaseline='top';\n    var k0=Math.ceil(view.s\/st-1e-9),k1=Math.floor((view.s+view.sp)\/st+1e-9),dd=st>=1?0:(st>=0.1?1:(st>=0.01?2:3));\n    for(var k=k0;k<=k1;k++){\n      var tt=k*st,tx=Math.round((tt-view.s)*pps*dpr);\n      g.fillStyle='rgba(255,255,255,.2)';g.fillRect(tx,rul-7*dpr,Math.max(1,Math.round(dpr)),7*dpr);\n      g.fillStyle='#8089a0';g.fillText(fmt(tt,dd),tx+4*dpr,5*dpr);\n    }\n    g.fillStyle='rgba(255,255,255,.09)';g.fillRect(0,Math.round(mid),cw,Math.max(1,Math.round(dpr)));\n    var vg=el.prev.checked?gainInfo.g:1,spc=view.sp*sr\/cw,s0=view.s*sr,len=buf.length,mn=bins.mn,mx=bins.mx,nb=bins.nb,last='',x,j,c;\n    var rev=el.rev.checked,SS=trimS*sr,EE=trimE*sr,gf=mkGain(trimS,trimE-trimS),datas=[];\n    for(c=0;c<chN;c++)datas.push(buf.getChannelData(c));\n    for(x=0;x<cw;x++){\n      var a=s0+x*spc,b=a+spc;\n      if(a>=len)break;\n      var ra=a,rb=b,fg=1,tcn=(a+b)\/2\/sr;\n      if(tcn>=trimS&&tcn<trimE){if(rev){ra=SS+EE-b;rb=SS+EE-a;}fg=gf(tcn);}\n      if(ra<0)ra=0;if(rb>len)rb=len;if(rb<=ra)rb=ra+1;\n      var lo=1,hi=-1;\n      if(spc>=BIN){\n        var ba=Math.floor(ra\/BIN);if(ba>=nb)continue;\n        var bb=Math.min(nb,Math.max(ba+1,Math.ceil(rb\/BIN)));\n        for(j=ba;j<bb;j++){if(mn[j]<lo)lo=mn[j];if(mx[j]>hi)hi=mx[j];}\n      }else{\n        var ia=Math.floor(ra),ib=Math.min(len,Math.max(ia+1,Math.ceil(rb)));\n        for(c=0;c<chN;c++){var d=datas[c];for(j=ia;j<ib;j++){var v=d[j];if(v<lo)lo=v;if(v>hi)hi=v;}}\n      }\n      if(hi<lo)continue;\n      var kk=vg*fg;lo*=kk;hi*=kk;\n      var clip=hi>1||lo<-1,col=clip?'#ff4d6d':'#ff0000';\n      if(hi>1)hi=1;if(lo<-1)lo=-1;\n      var y1=mid-hi*amp,y2=mid-lo*amp;\n      if(col!==last){g.fillStyle=col;last=col;}\n      g.fillRect(x,y1,1,Math.max(dpr,y2-y1));\n    }\n  }\n  function drawOv(){\n    var cv=el.ov,w=W(),h=H();\n    if(!w||!h)return;\n    var dpr=Math.min(window.devicePixelRatio||1,2),cw=Math.round(w*dpr),ch=Math.round(h*dpr);\n    if(cv.width!==cw||cv.height!==ch){cv.width=cw;cv.height=ch;}\n    var g=cv.getContext('2d');g.setTransform(dpr,0,0,dpr,0,0);g.clearRect(0,0,w,h);\n    if(!buf)return;\n    var em=tool==='env',i,y;\n    g.font='10px -apple-system,Segoe UI,Roboto,sans-serif';g.textBaseline='alphabetic';\n    if(em||env.length){\n      var marks=em?[12,6,0,-6,-12,-24,-36,-48]:[0];\n      g.lineWidth=1;g.textAlign='right';\n      for(i=0;i<marks.length;i++){\n        y=Math.round(dbToY(marks[i]))+.5;\n        g.strokeStyle=marks[i]===0?'rgba(255,209,102,.38)':'rgba(255,209,102,.12)';\n        g.setLineDash(marks[i]===0?[5,4]:[2,4]);\n        g.beginPath();g.moveTo(0,y);g.lineTo(w,y);g.stroke();\n        if(em){g.fillStyle='rgba(255,209,102,.7)';g.fillText(marks[i]===0?'0 dB':(marks[i]>0?'+':'\u2212')+Math.abs(marks[i]),w-6,y-3);}\n      }\n      g.setLineDash([]);\n    }\n    if(env.length){\n      g.strokeStyle='#ffd166';g.lineWidth=em?2:1.5;g.globalAlpha=em?1:.75;\n      g.beginPath();g.moveTo(-5,dbToY(env[0].db));\n      for(i=0;i<env.length;i++)g.lineTo(X(env[i].t),dbToY(env[i].db));\n      g.lineTo(w+5,dbToY(env[env.length-1].db));g.stroke();g.globalAlpha=1;\n      for(i=0;i<env.length;i++){\n        var px=X(env[i].t),py=dbToY(env[i].db);\n        if(px<-8||px>w+8)continue;\n        g.beginPath();g.arc(px,py,em?5.5:3.5,0,6.2832);\n        g.fillStyle=(env[i]===selPt&&em)?'#ffffff':'#ffd166';g.fill();\n        g.lineWidth=2;g.strokeStyle='#0b0c10';g.stroke();\n      }\n      if(em&&selPt){\n        var lx=X(selPt.t),ly=dbToY(selPt.db),txt=(selPt.db<=DBMIN?'\u2212\u221e dB':(selPt.db>0?'+':'')+selPt.db.toFixed(1)+' dB')+' \u00b7 '+fmt(selPt.t,2);\n        g.textAlign='left';\n        var tw=g.measureText(txt).width+10,bx=Math.min(Math.max(2,lx+8),w-tw-2),by=ly-24<RUL+2?ly+10:ly-24;\n        g.fillStyle='rgba(11,12,16,.92)';g.fillRect(bx,by,tw,16);\n        g.fillStyle='#ffd166';g.fillText(txt,bx+5,by+11.5);\n      }\n    }\n    var hy=RUL+HYO,xs=X(trimS),xe=X(trimE),xfi=X(trimS+fadeIn),xfo=X(trimE-fadeOut);\n    g.lineWidth=1.5;g.strokeStyle='rgba(255,255,255,.85)';\n    if(fadeIn>0){g.beginPath();g.moveTo(xs,h);g.lineTo(xfi,hy);g.stroke();}\n    if(fadeOut>0){g.beginPath();g.moveTo(xfo,hy);g.lineTo(xe,h);g.stroke();}\n    [xfi,xfo].forEach(function(hx){\n      if(hx<-12||hx>w+12)return;\n      g.beginPath();g.arc(hx,hy,6,0,6.2832);g.fillStyle='#fff';g.fill();\n      g.lineWidth=2;g.strokeStyle='#00e5ff';g.stroke();\n    });\n  }\n  function place(e,x,off){\n    var w=W();\n    if(x<-20||x>w+20){e.style.display='none';return;}\n    e.style.display='block';e.style.transform='translateX('+(x-off)+'px)';\n  }\n  function placePH(){place(el.ph,X(getPos()),1);}\n  function layout(){\n    if(!buf)return;\n    var w=W(),xs=X(trimS),xe=X(trimE),rl=clamp(xe,0,w);\n    el.shl.style.width=clamp(xs,0,w)+'px';\n    el.shr.style.left=rl+'px';el.shr.style.width=(w-rl)+'px';\n    place(el.hs,xs,8);place(el.he,xe,8);placePH();\n  }\n  function setView(s,sp){\n    if(!buf)return;\n    sp=Math.min(dur,Math.max(Math.min(dur,0.01),sp));\n    s=clamp(s,0,dur-sp);\n    view.s=s;view.sp=sp;\n    var z=sp<dur-1e-6;\n    el.scroll.hidden=!z;\n    if(z){el.scroll.value=Math.round(s\/(dur-sp)*1000);el.scroll.style.setProperty('--p',(s\/(dur-sp)*100)+'%');}\n    invalidate(true,true);\n  }\n  function zoomAt(f,c){\n    var sp=Math.min(dur,Math.max(0.01,view.sp*f));\n    setView(c-(c-view.s)*(sp\/view.sp),sp);\n  }\n  \/* ---------- Transport ---------- *\/\n  function rawPos(){return p0+(ctx.currentTime-t0);}\n  function getPos(){return playing&&ctx?Math.min(trimE,rawPos()):pos;}\n  function setPlayUI(on){el.play.classList.toggle('aew-playing',on);el.play.setAttribute('aria-label',on?'Pause':'Afspil');}\n  function ensureRev(){\n    var key=trimS+'|'+trimE;\n    if(revBuf&&revKey===key)return revBuf;\n    var s0=Math.max(0,Math.floor(trimS*sr)),s1=Math.min(buf.length,Math.ceil(trimE*sr)),n=Math.max(1,s1-s0),rb=ctx.createBuffer(chN,n,sr),c,i;\n    for(c=0;c<chN;c++){var a=buf.getChannelData(c),d=rb.getChannelData(c);for(i=0;i<n;i++)d[i]=a[s1-1-i];}\n    revBuf=rb;revKey=key;return rb;\n  }\n  function scheduleFx(){\n    if(!fxNode||!playing||!ctx)return;\n    var now=ctx.currentTime,p=Math.min(trimE,rawPos()),end=Math.min(trimE,p+FXWIN),gp=fxNode.gain,span=end-p,gf=mkGain(trimS,trimE-trimS);\n    gp.cancelScheduledValues(now);\n    gp.setValueAtTime(gf(p),now);\n    fxEnd=end;\n    if(span<0.004)return;\n    var n=Math.max(2,Math.ceil(span*FXRATE)),i;\n    for(i=1;i<=n;i++)gp.linearRampToValueAtTime(gf(p+span*i\/n),now+span*i\/n);\n  }\n  function stopSource(){\n    var s=src;src=null;\n    if(s){s.onended=null;try{s.stop();}catch(e){}try{s.disconnect();}catch(e){}}\n    if(fxNode&&ctx){var gp=fxNode.gain;gp.cancelScheduledValues(0);gp.setValueAtTime(1,ctx.currentTime);}\n  }\n  function startSource(){\n    if(!buf)return;\n    if(pos<trimS||pos>=trimE-0.01)pos=trimS;\n    stopSource();\n    var rev=el.rev.checked,sb=rev?ensureRev():buf,off=Math.max(0,rev?pos-trimS:pos);\n    var me=ctx.createBufferSource();me.buffer=sb;me.connect(gainNode);src=me;\n    me.onended=function(){\n      if(src!==me)return;\n      src=null;playing=false;pos=trimE;\n      if(el.loop.checked){pos=trimS;play();}\n      else{setPlayUI(false);frameOnce();}\n    };\n    p0=pos;t0=ctx.currentTime;\n    me.start(0,off,Math.max(0.001,trimE-pos));\n    playing=true;setPlayUI(true);scheduleFx();loopFrame();\n  }\n  function play(){\n    if(!buf)return;\n    if(ctx.state==='running')startSource();else ctx.resume().then(startSource);\n  }\n  function pause(){\n    if(playing){pos=getPos();stopSource();playing=false;}\n    setPlayUI(false);frameOnce();\n  }\n  function toggle(){if(playing)pause();else play();}\n  function restart(){\n    if(!playing)return;\n    var p=rawPos();stopSource();pos=p;\n    if(p>=trimE-0.01){playing=false;pos=trimE;setPlayUI(false);frameOnce();}\n    else startSource();\n  }\n  function seekTo(t){\n    var was=playing;\n    if(was){pos=getPos();stopSource();playing=false;}\n    pos=clamp(t,0,dur);\n    if(was)play();else frameOnce();\n  }\n  function frameOnce(){\n    if(!buf)return;\n    var p=getPos();\n    if(playing&&!drag&&view.sp<dur-1e-6&&(p>view.s+view.sp*0.98||p<view.s)){setView(p-view.sp*0.05,view.sp);}\n    placePH();\n    el.tcur.textContent=fmt(p,2);\n  }\n  function tickFrame(){\n    raf=0;frameOnce();\n    if(playing){\n      if(fxEnd<trimE-0.001&&rawPos()>fxEnd-3)scheduleFx();\n      raf=requestAnimationFrame(tickFrame);\n    }\n  }\n  function loopFrame(){if(!raf)raf=requestAnimationFrame(tickFrame);}\n  \/* ---------- Trim ---------- *\/\n  function updateOut(){\n    if(!buf){el.out.textContent='\u2013';return;}\n    var d=trimE-trimS,f=el.fmt.value,bytes;\n    if(f==='mp3')bytes=(+el.kbps.value)*1000\/8*d;\n    else bytes=44+d*sr*chN*(f==='wav24'?3:2);\n    var sz=bytes>=1048576?(bytes\/1048576).toFixed(1)+' MB':Math.max(1,Math.round(bytes\/1024))+' KB';\n    el.out.textContent='Output: '+fmt(d,1)+' \u00b7 '+(chN>1?'stereo':'mono')+' \u00b7 '+Math.round(sr)+' Hz \u00b7 ca. '+sz+(el.rev.checked?' \u00b7 reversed':'');\n  }\n  function trimUI(){\n    clampFades();\n    el.fs.value=fmt(trimS,3);el.fe.value=fmt(trimE,3);el.len.textContent=fmt(trimE-trimS,3);\n    fadeUI();updateOut();invalidate(true,true);\n  }\n  function onTrim(commit){\n    trimUI();scheduleMeasure();\n    if(commit)restart();\n  }\n  function commitField(which){\n    var inp=which==='s'?el.fs:el.fe,v=parseT(inp.value);\n    if(v==null||!buf){trimUI();return;}\n    if(which==='s')trimS=clamp(v,0,trimE-MIN);else trimE=clamp(v,trimS+MIN,dur);\n    onTrim(true);\n  }\n  function setStartHere(){\n    if(!buf)return;var t=getPos();\n    if(t>trimE-MIN){say('Start skal ligge f\u00f8r slut.',true);return;}\n    trimS=Math.max(0,t);onTrim(true);say('Start sat til '+fmt(trimS,3)+'.');\n  }\n  function setEndHere(){\n    if(!buf)return;var t=getPos();\n    if(t<trimS+MIN){say('Slut skal ligge efter start.',true);return;}\n    trimE=Math.min(dur,t);onTrim(true);say('Slut sat til '+fmt(trimE,3)+'.');\n  }\n  \/* ---------- Pointer ---------- *\/\n  function ptr(e){var r=el.wave.getBoundingClientRect();return {x:e.clientX-r.left,y:e.clientY-r.top};}\n  function hitFade(x,y){\n    var hy=RUL+HYO,a=Math.hypot(x-X(trimS+fadeIn),y-hy),b=Math.hypot(x-X(trimE-fadeOut),y-hy);\n    if(a>11&&b>11)return '';\n    if(a===b)return x<X(trimS+fadeIn)?'fi':'fo';\n    return a<b?'fi':'fo';\n  }\n  function hitTrim(x,y){\n    var xs=X(trimS),xe=X(trimE),ds=Math.abs(x-xs),de=Math.abs(x-xe),r=14;\n    if(tool==='env'&&y>=RUL)return '';\n    if(ds>r&&de>r)return '';\n    if(Math.abs(ds-de)<0.5)return x<xs?'s':'e';\n    return ds<de?'s':'e';\n  }\n  function dragMove(x,y){\n    var t=clamp(T(x),0,dur),L=trimE-trimS;\n    if(drag==='s'){trimS=clamp(t,0,trimE-MIN);onTrim(false);}\n    else if(drag==='e'){trimE=clamp(t,trimS+MIN,dur);onTrim(false);}\n    else if(drag==='fi'){fadeIn=clamp(t-trimS,0,L-fadeOut);if(fadeIn<0.005)fadeIn=0;fadeUI();scheduleFx();invalidate(true,true);}\n    else if(drag==='fo'){fadeOut=clamp(trimE-t,0,L-fadeIn);if(fadeOut<0.005)fadeOut=0;fadeUI();scheduleFx();invalidate(true,true);}\n    else if(drag==='pt'&&dragPt){dragPt.t=t;dragPt.db=snapDb(yToDb(y));sortEnv();envChanged();}\n    else if(drag==='sel'){\n      if(Math.abs(x-selX)>2)selMoved=true;\n      if(selMoved){\n        var lo=Math.min(selA,t),hi=Math.max(selA,t);\n        if(hi-lo<MIN){if(lo+MIN<=dur)hi=lo+MIN;else lo=hi-MIN;}\n        trimS=Math.max(0,lo);trimE=Math.min(dur,hi);onTrim(false);\n      }\n    }\n    else{pos=t;frameOnce();}\n  }\n  el.wave.addEventListener('pointerdown',function(e){\n    if(!buf||e.button>0)return;\n    var p=ptr(e),x=p.x,y=p.y,h;\n    try{el.wave.setPointerCapture(e.pointerId);}catch(_){}\n    try{el.wave.focus({preventScroll:true});}catch(_){}\n    wasPlaying=false;\n    if(e.shiftKey){drag='sel';selA=clamp(T(x),0,dur);selX=x;selMoved=false;e.preventDefault();return;}\n    h=hitFade(x,y);\n    if(h){drag=h;dragMove(x,y);e.preventDefault();return;}\n    h=hitTrim(x,y);\n    if(h){drag=h;dragMove(x,y);e.preventDefault();return;}\n    if(tool==='env'&&y>=RUL){\n      var i=hitPt(x,y),pp;\n      if(i>=0)pp=env[i];else pp=addPt(T(x),yToDb(y));\n      selPt=pp;dragPt=pp;drag='pt';envChanged();e.preventDefault();return;\n    }\n    drag='p';\n    if(playing){wasPlaying=true;pause();}\n    dragMove(x,y);e.preventDefault();\n  });\n  el.wave.addEventListener('pointermove',function(e){\n    if(!buf)return;\n    var p=ptr(e);\n    if(drag){dragMove(p.x,p.y);return;}\n    var c='default';\n    if(e.shiftKey)c='crosshair';\n    else if(hitFade(p.x,p.y))c='grab';\n    else if(hitTrim(p.x,p.y))c='ew-resize';\n    else if(tool==='env'&&p.y>=RUL)c=hitPt(p.x,p.y)>=0?'move':'crosshair';\n    el.wave.style.cursor=c;\n  });\n  function endDrag(){\n    if(!drag)return;\n    var d=drag;drag=null;dragPt=null;\n    if(d==='p'){if(wasPlaying)play();}\n    else if(d==='s'||d==='e'){onTrim(true);}\n    else if(d==='sel'){\n      if(selMoved){\n        onTrim(true);\n        if(!playing){pos=trimS;frameOnce();}\n        say('Omr\u00e5de valgt: '+fmt(trimS,3)+' \u2013 '+fmt(trimE,3)+'.');\n      }\n    }\n    else if(d==='fi'||d==='fo'){scheduleFx();}\n    wasPlaying=false;invalidate(false,true);\n  }\n  el.wave.addEventListener('pointerup',endDrag);\n  el.wave.addEventListener('pointercancel',endDrag);\n  el.wave.addEventListener('dblclick',function(e){\n    if(!buf||tool!=='env')return;\n    var p=ptr(e),i=hitPt(p.x,p.y);\n    if(i>=0){var r=env[i];env.splice(i,1);if(selPt===r)selPt=null;envChanged();}\n  });\n  el.wave.addEventListener('wheel',function(e){\n    if(!buf)return;\n    var r=el.wave.getBoundingClientRect();\n    if(e.ctrlKey||e.metaKey){e.preventDefault();zoomAt(e.deltaY>0?1.25:0.8,T(e.clientX-r.left));}\n    else if(view.sp<dur-1e-6&&(e.shiftKey||Math.abs(e.deltaX)>Math.abs(e.deltaY))){e.preventDefault();setView(view.s+(e.deltaX||e.deltaY)\/W()*view.sp,view.sp);}\n  },{passive:false});\n  el.wave.addEventListener('keydown',function(e){\n    if(!buf)return;var k=e.key;\n    if(k===' '){e.preventDefault();toggle();}\n    else if(k==='i'||k==='I'){e.preventDefault();setStartHere();}\n    else if(k==='o'||k==='O'){e.preventDefault();setEndHere();}\n    else if(k==='e'||k==='E'){e.preventDefault();setTool(tool==='env'?'sel':'env');}\n    else if(k==='Delete'||k==='Backspace'){if(selPt){e.preventDefault();delSel();}}\n    else if(k==='Home'){e.preventDefault();seekTo(trimS);}\n    else if(k==='ArrowLeft'){e.preventDefault();seekTo(getPos()-(e.shiftKey?5:1));}\n    else if(k==='ArrowRight'){e.preventDefault();seekTo(getPos()+(e.shiftKey?5:1));}\n  });\n  \/* ---------- Indl\u00e6sning ---------- *\/\n  async function load(file){\n    if(!file)return;\n    if(!\/^audio\\\/\/.test(file.type)&&!\/\\.(mp3|wav|m4a|aac|ogg|flac)$\/i.test(file.name)){say('Ikke en underst\u00f8ttet lydfil. Brug MP3, WAV eller M4A.',true);return;}\n    var id=++loadId;\n    pause();say('Afkoder lyd \u2026');\n    try{\n      ensureCtx();\n      var ab=await readFile(file),b=await decode(ab);\n      if(id!==loadId)return;\n      buf=b;sr=b.sampleRate;dur=b.duration;chN=Math.min(2,b.numberOfChannels);MIN=Math.min(0.02,dur\/2);\n      fileName=file.name.replace(\/\\.[^.]+$\/,'')||'lyd';\n      kw=null;buildBins();\n      trimS=0;trimE=dur;pos=0;fadeIn=0;fadeOut=0;env=[];selPt=null;dragPt=null;revBuf=null;revKey='';el.rev.checked=false;\n      meas={peak:peakIn(0,dur),lufs:null};\n      root.classList.remove('aew-blank');el.work.inert=false;\n      el.drop.classList.add('aew-has');\n      $('.aew-dn').textContent=file.name;\n      $('.aew-ds').textContent=' \u00b7 '+fmt(dur,1)+' \u00b7 '+Math.round(sr)+' Hz \u00b7 '+(b.numberOfChannels>1?'stereo':'mono')+' \u00b7 klik for at skifte fil';\n      el.tt.textContent=fmt(dur,2);\n      setTool('sel');envUI();\n      view={s:0,sp:dur};setView(0,dur);trimUI();applyGain(true);frameOnce();\n      say('M\u00e5ler loudness \u2026');\n      var k=await kweight(b);\n      if(id!==loadId)return;\n      kw=k;measure();say('Klar \u2013 tr\u00e6k i h\u00e5ndtagene, eller hold Shift og tr\u00e6k for at v\u00e6lge et omr\u00e5de.');\n    }catch(e){\n      if(id===loadId)say('Kunne ikke afkode filen'+(e&&e.message?': '+e.message:'.'),true);\n    }\n  }\n  \/* ---------- Eksport ---------- *\/\n  function save(blob,name){\n    var u=URL.createObjectURL(blob),a=document.createElement('a');\n    a.href=u;a.download=name;document.body.appendChild(a);a.click();document.body.removeChild(a);\n    setTimeout(function(){URL.revokeObjectURL(u);},4000);\n  }\n  function makeRenderer(){\n    var s0=Math.max(0,Math.round(trimS*sr)),s1=Math.min(buf.length,Math.round(trimE*sr)),n=s1-s0;\n    if(n<1)throw new Error('Det trimmede omr\u00e5de er tomt.');\n    var chs=[],c,rev=el.rev.checked,ng=gainInfo.g,gf=mkGain(s0\/sr,n\/sr);\n    for(c=0;c<chN;c++)chs.push(buf.getChannelData(c));\n    return {n:n,chunk:function(i0,m){\n      var out=[],i,x,cc;\n      for(cc=0;cc<chN;cc++)out.push(new Float32Array(m));\n      for(i=0;i<m;i++){\n        var oi=i0+i,si=rev?s1-1-oi:s0+oi,g=ng*gf((s0+oi)\/sr);\n        for(cc=0;cc<chN;cc++){x=chs[cc][si]*g;out[cc][i]=x>1?1:(x<-1?-1:x);}\n      }\n      return out;\n    }};\n  }\n  async function encWav(R,bits){\n    var n=R.n,nc=chN,bps=bits\/8,dl=n*nc*bps,ab=new ArrayBuffer(44+dl),dv=new DataView(ab);\n    function ws(o,s){for(var i=0;i<s.length;i++)dv.setUint8(o+i,s.charCodeAt(i));}\n    ws(0,'RIFF');dv.setUint32(4,36+dl,true);ws(8,'WAVE');ws(12,'fmt ');dv.setUint32(16,16,true);dv.setUint16(20,1,true);dv.setUint16(22,nc,true);\n    dv.setUint32(24,sr,true);dv.setUint32(28,sr*nc*bps,true);dv.setUint16(32,nc*bps,true);dv.setUint16(34,bits,true);ws(36,'data');dv.setUint32(40,dl,true);\n    var o=44,CH=131072;\n    for(var i0=0;i0<n;i0+=CH){\n      var m=Math.min(CH,n-i0),ck=R.chunk(i0,m),i,c;\n      for(i=0;i<m;i++){\n        for(c=0;c<nc;c++){\n          var x=ck[c][i];\n          if(bits===16){dv.setInt16(o,Math.round(x<0?x*32768:x*32767),true);o+=2;}\n          else{var v=Math.round(x<0?x*8388608:x*8388607);dv.setUint8(o,v&255);dv.setUint8(o+1,(v>>8)&255);dv.setUint8(o+2,(v>>16)&255);o+=3;}\n        }\n      }\n      say('Eksporterer WAV \u2026 '+Math.round((i0+m)\/n*100)+'%');\n      await tick();\n    }\n    return new Blob([ab],{type:'audio\/wav'});\n  }\n  async function encMp3(Lm,R,kbps){\n    if([32000,44100,48000].indexOf(sr)<0)throw new Error('MP3 underst\u00f8tter ikke '+Math.round(sr)+' Hz her \u2013 brug WAV.');\n    var n=R.n,nc=chN,enc=new Lm.Mp3Encoder(nc,sr,kbps),out=[],BL=1152*32;\n    function toI(a){var o=new Int16Array(a.length),i,x;for(i=0;i<a.length;i++){x=a[i];o[i]=Math.round(x<0?x*32768:x*32767);}return o;}\n    for(var i0=0;i0<n;i0+=BL){\n      var m=Math.min(BL,n-i0),ck=R.chunk(i0,m),l=toI(ck[0]),r=nc>1?toI(ck[1]):null,b=nc>1?enc.encodeBuffer(l,r):enc.encodeBuffer(l);\n      if(b&&b.length)out.push(new Int8Array(b));\n      say('Eksporterer MP3 \u2026 '+Math.round((i0+m)\/n*100)+'%');\n      await tick();\n    }\n    var f=enc.flush();if(f&&f.length)out.push(new Int8Array(f));\n    return new Blob(out,{type:'audio\/mpeg'});\n  }\n  \/* \"Gem som\": rigtig arkivdialog hvor browseren kan (Chrome\/Edge\/Opera), ellers en navne-dialog i widgetten *\/\n  function cleanName(v,ext){\n    v=String(v||'').replace(\/[\\\\\\\/:*?\"<>|\\u0000-\\u001f]\/g,'').trim().replace(\/[. ]+$\/,'');\n    v=v.replace(new RegExp('\\\\.'+ext+'$','i'),'');\n    if(!v)v=fileName+'-edit';\n    return v+'.'+ext;\n  }\n  function closeDlg(r){el.dlg.hidden=true;var f=dlgRes;dlgRes=null;if(f)f(r);}\n  function askName(sugg,fm){\n    return new Promise(function(res){\n      if(dlgRes)closeDlg(null);\n      dlgRes=res;dlgFm=fm;\n      el.dlgName.value=sugg.replace(\/\\.[^.]+$\/,'');\n      el.dlgExt.textContent='.'+fm.ext;\n      el.dlg.hidden=false;\n      setTimeout(function(){el.dlgName.focus();el.dlgName.select();},30);\n    });\n  }\n  function chooseTarget(sugg,fm){\n    if(typeof window.showSaveFilePicker==='function'){\n      var acc={},pr;acc[fm.mime]=['.'+fm.ext];\n      try{pr=window.showSaveFilePicker({suggestedName:sugg,types:[{description:fm.desc,accept:acc}]});}\n      catch(e){return askName(sugg,fm);}\n      return pr.then(function(h){return {handle:h};},function(e){if(e&&e.name==='AbortError')return null;return askName(sugg,fm);});\n    }\n    return askName(sugg,fm);\n  }\n  async function exportAudio(){\n    if(!buf||busy)return;\n    busy=true;el.exp.disabled=true;\n    try{\n      var f=el.fmt.value,isMp3=f==='mp3';\n      var fm=isMp3?{ext:'mp3',mime:'audio\/mpeg',desc:'MP3-lydfil'}:{ext:'wav',mime:'audio\/wav',desc:'WAV-lydfil'};\n      var R=makeRenderer();\n      if(isMp3&&!lameReady()){say('Henter MP3-encoder \u2026');await loadLame();}\n      var target=await chooseTarget(fileName+'-edit.'+fm.ext,fm);\n      if(!target){say('Eksport annulleret.');return;}\n      var blob=isMp3?await encMp3(await loadLame(),R,+el.kbps.value):await encWav(R,f==='wav24'?24:16);\n      if(target.handle){\n        var w=await target.handle.createWritable();await w.write(blob);await w.close();\n        say('Gemt: '+(target.handle.name||'fil'));\n      }else{\n        save(blob,target.name);say('Eksporteret: '+target.name+' (lagt i din download-mappe)');\n      }\n    }catch(e){say('Eksport mislykkedes: '+(e&&e.message?e.message:'ukendt fejl'),true);}\n    finally{busy=false;el.exp.disabled=false;}\n  }\n  \/* ---------- Events ---------- *\/\n  el.file.addEventListener('change',function(){var f=el.file.files&&el.file.files[0];if(f)load(f);try{el.file.value='';}catch(_){}});\n  function hasFiles(e){var t=e.dataTransfer&&e.dataTransfer.types;return !!t&&Array.prototype.indexOf.call(t,'Files')>=0;}\n  ['dragenter','dragover'].forEach(function(t){root.addEventListener(t,function(e){if(!hasFiles(e))return;e.preventDefault();el.drop.classList.add('aew-over');});});\n  root.addEventListener('dragleave',function(e){if(e.target===el.drop||e.target===root)el.drop.classList.remove('aew-over');});\n  root.addEventListener('drop',function(e){\n    if(!hasFiles(e))return;\n    e.preventDefault();el.drop.classList.remove('aew-over');\n    var f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f)load(f);\n  });\n  el.play.addEventListener('click',toggle);\n  el.stop.addEventListener('click',function(){pause();pos=trimS;frameOnce();});\n  el.sets.addEventListener('click',setStartHere);\n  el.sete.addEventListener('click',setEndHere);\n  el.reset.addEventListener('click',function(){if(!buf)return;trimS=0;trimE=dur;onTrim(true);});\n  el.fs.addEventListener('change',function(){commitField('s');});\n  el.fe.addEventListener('change',function(){commitField('e');});\n  [el.fs,el.fe].forEach(function(i){i.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault();i.blur();}});});\n  el.zin.addEventListener('click',function(){var p=getPos();zoomAt(0.5,(p>=view.s&&p<=view.s+view.sp)?p:view.s+view.sp\/2);});\n  el.zout.addEventListener('click',function(){zoomAt(2,view.s+view.sp\/2);});\n  el.zfit.addEventListener('click',function(){setView(0,dur);});\n  el.zsel.addEventListener('click',function(){var pad=(trimE-trimS)*0.05+0.01;setView(trimS-pad,trimE-trimS+2*pad);});\n  el.scroll.addEventListener('input',function(){setView(el.scroll.value\/1000*(dur-view.sp),view.sp);});\n  el.toolSel.addEventListener('click',function(){setTool('sel');});\n  el.toolEnv.addEventListener('click',function(){setTool('env');});\n  el.envdel.addEventListener('click',delSel);\n  el.envclr.addEventListener('click',function(){env=[];selPt=null;envChanged();});\n  el.fres.addEventListener('click',function(){fadeIn=0;fadeOut=0;fadeUI();scheduleFx();invalidate(true,true);});\n  el.rev.addEventListener('change',function(){\n    if(!buf)return;\n    var was=playing,p=getPos();\n    if(was)pause();\n    pos=clamp(trimS+trimE-p,trimS,trimE);\n    if(!el.rev.checked){revBuf=null;revKey='';}\n    updateOut();invalidate(true,true);\n    if(was)play();else frameOnce();\n  });\n  [el.mode,el.tp,el.tl,el.tcl,el.prev].forEach(function(x){\n    x.addEventListener('input',function(){sync();applyGain(true);});\n    x.addEventListener('change',function(){sync();applyGain(true);});\n  });\n  el.fmt.addEventListener('change',function(){sync();updateOut();if(el.fmt.value==='mp3')loadLame().catch(function(){});});\n  el.kbps.addEventListener('change',updateOut);\n  el.exp.addEventListener('click',exportAudio);\n  el.dlgOk.addEventListener('click',function(){closeDlg({name:cleanName(el.dlgName.value,dlgFm?dlgFm.ext:'wav')});});\n  el.dlgCancel.addEventListener('click',function(){closeDlg(null);});\n  el.dlgName.addEventListener('keydown',function(e){\n    if(e.key==='Enter'){e.preventDefault();el.dlgOk.click();}\n    else if(e.key==='Escape'){e.preventDefault();closeDlg(null);}\n  });\n  if(window.ResizeObserver)new ResizeObserver(function(){invalidate(true,true);}).observe(el.wave);\n  else window.addEventListener('resize',function(){invalidate(true,true);});\n  sync();setStats();updateOut();fadeUI();envUI();helpTxt();\n}\nfunction fail(root,e){\n  var s=root.querySelector('.aew-status');\n  if(s){s.textContent='Fejl ved start: '+(e&&e.message?e.message:e);s.classList.add('aew-err');}\n  if(window.console&&console.error)console.error(e);\n}\nfunction boot(){var n=document.querySelectorAll('.audio-editor-widget');for(var i=0;i<n.length;i++){try{init(n[i]);}catch(e){fail(n[i],e);}}}\nif(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot);else boot();\n})();\n<\/script>\n<\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Audio Editor 100% lokalt \u00b7 ingen upload Tr\u00e6k en lydfil hertilMP3, WAV eller M4A \u2013 eller klik for at v\u00e6lge Klar \u2013 slip en lydfil for at begynde. \u2212 + Fit Zoom til markering Mark\u00f8r Envelope Slip en lydfil ovenfor for at begynde \u25a0 Stop Gentag markering 0:00.00 \/ 0:00.00 S\u00e6t start (I) S\u00e6t slut&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"hide","_kad_post_layout":"fullwidth","_kad_post_sidebar_id":"","_kad_post_content_style":"unboxed","_kad_post_vertical_padding":"hide","_kad_post_feature":"hide","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-13","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=\/wp\/v2\/pages\/13","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=13"}],"version-history":[{"count":17,"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=\/wp\/v2\/pages\/13\/revisions"}],"predecessor-version":[{"id":39,"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=\/wp\/v2\/pages\/13\/revisions\/39"}],"wp:attachment":[{"href":"https:\/\/audio.anderskisling.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=13"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}