{"id":20215,"date":"2026-09-23T20:42:55","date_gmt":"2026-09-23T18:42:55","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/"},"modified":"2026-09-23T21:54:49","modified_gmt":"2026-09-23T19:54:49","slug":"click-splice-finder-hard-cuts-slope-breaks","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/","title":{"rendered":"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file"},"content":{"rendered":"<div class=\"lw-kf\" id=\"kf-wurzel\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<style>\n.lw-kf { --kf-warn: #e8a33d; }\n:root[data-theme=\"light\"] .lw-kf { --kf-warn: #8f5300; }\n.lw-kf .kf-zone { border: 2px dashed var(--border, #2a2a35); border-radius: 8px; padding: 22px; text-align: center; }\n.lw-kf .kf-zone.kf-ueber { border-color: var(--accent, #2ba6df); }\n.lw-kf .kf-kacheln { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 4px 0; }\n.lw-kf .kf-kachel { background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 11px 14px; min-width: 0; }\n.lw-kf .kf-kachel-name { font-size: 0.74rem; color: var(--text-secondary, #a0a0b0); text-transform: uppercase; letter-spacing: 0.4px; }\n.lw-kf .kf-kachel-wert { font-family: monospace; font-size: 1.4rem; font-weight: 700; color: var(--text-primary, #e8e8ee); line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.lw-kf .kf-urteil { border-radius: 8px; padding: 12px 15px; margin-top: 14px; border: 1px solid var(--border, #2a2a35); background: var(--bg-body, #14141a); }\n.lw-kf .kf-urteil-kopf { font-weight: 700; color: var(--text-primary, #e8e8ee); margin-bottom: 4px; }\n.lw-kf .kf-u-sauber .kf-urteil-kopf { color: var(--ok, #7ee787); }\n.lw-kf .kf-u-stellen .kf-urteil-kopf { color: var(--kf-warn); }\n.lw-kf .kf-tabelle { overflow-x: auto; margin-top: 6px; }\n.lw-kf table { border-collapse: collapse; width: 100%; font-size: 0.86rem; }\n.lw-kf th, .lw-kf td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border, #2a2a35); vertical-align: top; }\n.lw-kf th { color: var(--text-primary, #e8e8ee); white-space: nowrap; }\n.lw-kf td { color: var(--text-primary, #e8e8ee); }\n.lw-kf td.kf-wert { font-family: monospace; color: var(--text-secondary, #a0a0b0); white-space: nowrap; }\n.lw-kf .kf-h { font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 24px 0 8px; }\n.lw-kf .kf-klein { font-size: 0.84rem; color: var(--text-secondary, #a0a0b0); line-height: 1.55; }\n.lw-kf .kf-knopf { background: transparent; color: var(--text-secondary, #a0a0b0); border: 1px solid var(--border, #2a2a35); padding: 9px 14px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; margin-top: 12px; }\n.lw-kf .kf-fehler { color: var(--fehler, #f0857a); font-size: 0.88rem; margin-top: 12px; }\n.lw-kf a { color: var(--accent, #2ba6df); }\n@media (max-width: 480px) {\n    .lw-kf .kf-kacheln { grid-template-columns: 1fr 1fr; gap: 8px; }\n    .lw-kf .kf-kachel-wert { font-size: 1.15rem; }\n}\n\/* Schmal: die Liste als gestapelte Karten *\/\n@media (max-width: 600px) {\n    .lw-kf table.kf-karten thead { display: none; }\n    .lw-kf table.kf-karten tr { display: block; padding: 8px 0; border-bottom: 1px solid var(--border, #2a2a35); }\n    .lw-kf table.kf-karten td { display: inline-block; border: none; padding: 1px 10px 1px 0; }\n    .lw-kf table.kf-karten td:first-child { display: block; font-weight: 700; }\n    .lw-kf table.kf-karten td[data-name]::before { content: attr(data-name) \": \"; color: var(--text-secondary, #a0a0b0); font-family: inherit; }\n}\n<\/style>\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 18px;\">Searches an audio file for clicks: hard cuts, single stray samples and places where the waveform suddenly changes direction. Every place comes with its time, how far it stands out from its surroundings, its level above 5\u00a0kHz and the fade length that would bury a click of that size. The file is read in the browser and not uploaded.<\/p>\n<div id=\"kf-zone\" class=\"kf-zone\">\n<p style=\"margin: 0 0 12px; color: var(--text-primary, #e8e8ee); font-weight: 700;\">Choose an audio file<\/p>\n<p>        <button id=\"kf-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;\">Select file<\/button><br \/>\n        <input id=\"kf-datei\" type=\"file\" accept=\"audio\/*,.wav,.wave,.aif,.aiff,.aifc,.flac,.mp3,.m4a,.ogg,.opus\" hidden><\/p>\n<p class=\"kf-klein\" style=\"margin: 12px 0 0;\">WAV and AIFF are read sample by sample; FLAC, MP3, M4A, Ogg and Opus go through the browser&#039;s decoder.<\/p>\n<\/p><\/div>\n<div id=\"kf-status\" class=\"kf-klein\" data-mp-mask style=\"margin-top: 12px;\" aria-live=\"polite\"><\/div>\n<div id=\"kf-fehler\" class=\"kf-fehler\" data-mp-mask hidden><\/div>\n<div id=\"kf-ausgabe\" data-mp-mask style=\"margin-top: 18px;\" aria-live=\"polite\" hidden>\n<div id=\"kf-kacheln\" class=\"kf-kacheln\"><\/div>\n<div id=\"kf-urteil\" class=\"kf-urteil\"><\/div>\n<div class=\"kf-h\">Places<\/div>\n<div class=\"kf-tabelle\">\n<table class=\"kf-karten\">\n<thead>\n<tr>\n<th>Time<\/th>\n<th>Kind<\/th>\n<th>Channel<\/th>\n<th>Stands out by<\/th>\n<th>Click above 5\u00a0kHz<\/th>\n<th>Fade that buries it<\/th>\n<\/tr>\n<\/thead>\n<tbody id=\"kf-liste\"><\/tbody>\n<\/table><\/div>\n<p id=\"kf-mehr\" class=\"kf-klein\" style=\"margin: 8px 0 0;\">\n<p>        <button id=\"kf-kopieren\" type=\"button\" class=\"kf-knopf\">Copy result<\/button><br \/>\n        <span id=\"kf-kopiert\" class=\"kf-klein\" style=\"margin-left: 10px;\"><\/span>\n    <\/div>\n<div class=\"kf-h\">How the search works<\/div>\n<p class=\"kf-klein\" style=\"margin: 0 0 6px;\">A linear predictor of order 16, fitted anew for every 2048 samples, predicts each sample from the previous ones. Tonal material disappears in the prediction error, a jump remains. A place is reported when the error in its 1\u00a0ms block stands at least 12\u00a0dB above the louder of the two 20\u00a0ms surroundings and is at least 18\u00a0dB sharper than the error around it within 1\u00a0ms. The second rule separates a single outlier from a percussive attack, which spreads over many samples.<\/p>\n<p class=\"kf-klein\" style=\"margin: 0 0 6px;\">A jump is a sudden change of value, as with a cut in the middle of a waveform. A bend keeps the value but reverses the direction: in the measurement for the article on crossfades, a cut exactly at a falling zero crossing still clicked at \u221237.44\u00a0dBFS. The level above 5\u00a0kHz is the same measure as in that article.<\/p>\n<p class=\"kf-klein\" style=\"margin: 0 0 6px;\">The fade length comes from the same measurement: a fade of 1\u00a0ms lowered the click by 33.08\u00a0dB, and above that every doubling brought another 6\u00a0dB, up to 59.35\u00a0dB at 20\u00a0ms, independent of the pitch of the material. The tool picks the shortest length that lowers the click by its excess over the surroundings plus 6\u00a0dB.<\/p>\n<p class=\"kf-klein\" style=\"margin: 0 0 6px;\">ffmpeg&#039;s adeclick works on the same principle and replaces outliers by interpolation. At its defaults, in a dense test mix without a single click, it still changed 10.6\u00a0% of all samples, and afterwards this search found 18 places there instead of one; in a pad it removed 15 of 20 cuts and changed 1.4\u00a0% of the samples.<\/p>\n<p style=\"font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 24px 0 8px;\">Articles on this<\/p>\n<p class=\"kf-klein\" style=\"margin: 0 0 6px;\">How loud the click at a splice is and which fade length buries it is measured in the article \u201c<span class=\"lw-artikel-geplant\">Crossfades and Cuts<\/span>\u201d.<\/p>\n<p class=\"kf-klein\" style=\"margin: 20px 0 0;\">Sources:<br \/>\n        <a href=\"https:\/\/www.image-line.com\/fl-studio-learning\/fl-studio-online-manual\/html\/chansettings_sampler.htm\" rel=\"noopener\">Image-Line: Sampler Channel Settings (Declicking)<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/ffmpeg.org\/ffmpeg-filters.html\" rel=\"noopener\">ffmpeg Filters Documentation (adeclick, acrossfade)<\/a>\n    <\/p>\n<p class=\"kf-klein\" style=\"font-size: 0.8rem; margin: 14px 0 0;\">In a dense test mix with sixteenth-note hi-hats, 7 of 20 hard cuts stood out and were reported; the rest were buried in the treble of the hats. Very hard intended attacks, such as a synthetic kick that starts with a jump, can be reported as well.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';\n    const T = {\"dezimal\":\".\",\"einleitung\":\"Searches an audio file for clicks: hard cuts, single stray samples and places where the waveform suddenly changes direction. Every place comes with its time, how far it stands out from its surroundings, its level above 5\u00a0kHz and the fade length that would bury a click of that size. The file is read in the browser and not uploaded.\",\"zone_titel\":\"Choose an audio file\",\"knopf_datei\":\"Select file\",\"zone_hinweis\":\"WAV and AIFF are read sample by sample; FLAC, MP3, M4A, Ogg and Opus go through the browser\\u0027s decoder.\",\"status_lesen\":\"Reading the file \u2026\",\"status_dekodieren\":\"Decoding \u2026\",\"status_suchen\":\"Searching \u2026\",\"k_stellen\":\"Places found\",\"k_arten\":\"Jumps \\\/ bends\",\"k_max\":\"Stands out by up to\",\"k_rand\":\"Hard start or end\",\"wert_weich\":\"none\",\"urteil_sauber\":\"No clicks found\",\"urteil_stellen\":\"Places found: {n}\",\"urteil_text_sauber\":\"No place stands out from its surroundings as a single outlier. A jump that is buried in the treble of the surrounding material is not reported; see the limits below.\",\"urteil_text_stellen\":\"Each place below is a single outlier that the preceding samples do not predict. For a cut, the listed fade length pushes a click of this size into the surroundings; a click within a recording needs a declicker or a manual repair.\",\"h_liste\":\"Places\",\"sp_zeit\":\"Time\",\"sp_art\":\"Kind\",\"sp_kanal\":\"Channel\",\"sp_ueber\":\"Stands out by\",\"sp_klick\":\"Click above 5\u00a0kHz\",\"sp_blende\":\"Fade that buries it\",\"art_sprung\":\"jump\",\"art_knick\":\"bend\",\"art_anfang\":\"hard start\",\"art_ende\":\"hard end\",\"blende_mehr\":\"more than 20\u00a0ms\",\"mehr\":\"The list shows {n} of {gesamt} places; the copied result contains the same selection.\",\"knopf_kopieren\":\"Copy result\",\"kopiert\":\"copied\",\"kopieren_fehler\":\"copying not possible\",\"bericht_kopf\":\"Click search\",\"fehler_format\":\"This file is not one of the formats read here: WAV, AIFF, FLAC, MP3, M4A, Ogg or Opus.\",\"fehler_lesen\":\"The file could not be read or decoded.\",\"fehler_kurz\":\"The file is too short to search.\",\"h_erklaerung\":\"How the search works\",\"erkl_verfahren\":\"A linear predictor of order 16, fitted anew for every 2048 samples, predicts each sample from the previous ones. Tonal material disappears in the prediction error, a jump remains. A place is reported when the error in its 1\u00a0ms block stands at least 12\u00a0dB above the louder of the two 20\u00a0ms surroundings and is at least 18\u00a0dB sharper than the error around it within 1\u00a0ms. The second rule separates a single outlier from a percussive attack, which spreads over many samples.\",\"erkl_art\":\"A jump is a sudden change of value, as with a cut in the middle of a waveform. A bend keeps the value but reverses the direction: in the measurement for the article on crossfades, a cut exactly at a falling zero crossing still clicked at \u221237.44\u00a0dBFS. The level above 5\u00a0kHz is the same measure as in that article.\",\"erkl_blende\":\"The fade length comes from the same measurement: a fade of 1\u00a0ms lowered the click by 33.08\u00a0dB, and above that every doubling brought another 6\u00a0dB, up to 59.35\u00a0dB at 20\u00a0ms, independent of the pitch of the material. The tool picks the shortest length that lowers the click by its excess over the surroundings plus 6\u00a0dB.\",\"erkl_adeclick\":\"ffmpeg\\u0027s adeclick works on the same principle and replaces outliers by interpolation. At its defaults, in a dense test mix without a single click, it still changed 10.6\u00a0% of all samples, and afterwards this search found 18 places there instead of one; in a pad it removed 15 of 20 cuts and changed 1.4\u00a0% of the samples.\",\"quellen_titel\":\"Sources\",\"grenze\":\"In a dense test mix with sixteenth-note hi-hats, 7 of 20 hard cuts stood out and were reported; the rest were buried in the treble of the hats. Very hard intended attacks, such as a synthetic kick that starts with a jump, can be reported as well.\",\"lesen_h\":\"Articles on this\",\"lesen_1_vor\":\"How loud the click at a splice is and which fade length buries it is measured in the article \u201c\",\"lesen_1_titel\":\"Crossfades and Cuts\",\"lesen_1_nach\":\"\u201d.\"};<\/p>\n<p>    \/\/ KERN-ANFANG ------------------------------------------------------------\n    \/\/ Knack- und Schnittstellen-Finder. Mass wie im Artikel \"Crossfades und Schnitte\":\n    \/\/ die Spitze oberhalb von 5 kHz, wo tiefe Toene nichts beitragen.\n    var HP_HZ = 5000;\n    var BLOCK_S = 0.001;           \/\/ 1-ms-Bloecke fuer Spitze und Umfeld\n    var UMFELD_S = 0.02;           \/\/ je 20 ms vor und nach der Stelle\n    var LUECKE_S = 0.002;          \/\/ die 2 ms um die Stelle zaehlen nicht zum Umfeld\n    var SCHWELLE_DB = 12;          \/\/ Ueberhoehung des Restfehlers ueber das lautere der beiden Umfelder\n    var SCHAERFE_DB = 18;          \/\/ Spitze gegen den RMS des Restfehlers in +-1 ms ohne die Spitze\n    \/\/ Kalibriert an Testdateien: Schnitte in einer Flaeche 18 bis 51 dB Ueberhoehung und 20 bis\n    \/\/ 29 dB Schaerfe; Hi-Hat- und Kick-Einsaetze erreichen Ueberhoehung, aber nur 8 bis 17,5 dB\n    \/\/ Schaerfe, weil sie Rauschstoesse ueber viele Werte sind und kein einzelner Ausreisser.\n    var BODEN_DBFS = -90;          \/\/ darunter wird nichts gemeldet\n    var ZUSAMMEN_S = 0.005;        \/\/ Treffer innerhalb 5 ms sind eine Stelle\n    var HOECHSTENS = 500;\n    \/\/ Blendenlaenge -> Senkung des Klicks gegen den harten Schnitt, gemessen im Artikel\n    \/\/ (220 Hz, Spitze oberhalb 5 kHz): -14,24 dBFS ohne Blende.\n    var BLENDE = [[0.1, 8.85], [0.25, 21.07], [0.5, 26.80], [1, 33.08], [2, 39.28], [5, 47.24], [10, 53.30], [20, 59.35]];<\/p>\n<p>    \/\/ Butterworth-Hochpass 4. Ordnung als zwei Biquads (RBJ), Q 0,5412 und 1,3066\n    function hochpass(fs, f0) {\n        return [0.5411961, 1.3065630].map(function (q) {\n            var w = 2 * Math.PI * f0 \/ fs, c = Math.cos(w), a = Math.sin(w) \/ (2 * q), a0 = 1 + a;\n            return { b0: (1 + c) \/ 2 \/ a0, b1: -(1 + c) \/ a0, b2: (1 + c) \/ 2 \/ a0, a1: -2 * c \/ a0, a2: (1 - a) \/ a0 };\n        });\n    }\n    function filtern(x, stufen) {\n        var y = new Float32Array(x.length), i, s;\n        for (i = 0; i < x.length; i++) { y[i] = x[i]; }\n        for (s = 0; s < stufen.length; s++) {\n            var k = stufen[s], z1 = 0, z2 = 0;\n            for (i = 0; i < y.length; i++) {\n                var v = y[i], o = k.b0 * v + z1;\n                z1 = k.b1 * v - k.a1 * o + z2;\n                z2 = k.b2 * v - k.a2 * o;\n                y[i] = o;\n            }\n        }\n        return y;\n    }\n    function median(werte) {\n        var a = Array.prototype.slice.call(werte).sort(function (x, y) { return x - y; });\n        return a.length ? a[Math.floor(a.length \/ 2)] : 0;\n    }\n    function db(v) { return v > 0 ? 20 * Math.log10(v) : -Infinity; }<\/p>\n<p>    \/\/ Lineare Praediktion: je Block von 2048 Werten ein Praediktor der Ordnung 16\n    \/\/ (Autokorrelation mit Hann-Fenster, Levinson-Durbin). Der Restfehler enthaelt, was\n    \/\/ sich aus den vorigen Werten nicht vorhersagen laesst - tonales Material faellt\n    \/\/ weg, ein Sprung bleibt stehen.\n    var LPC_ORDNUNG = 16, LPC_BLOCK = 2048;\n    function levinson(r, p) {\n        var a = new Float64Array(p + 1), e = r[0], i, j;\n        a[0] = 1;\n        if (e <= 0) { return a; }\n        for (i = 1; i <= p; i++) {\n            var acc = r[i];\n            for (j = 1; j < i; j++) { acc += a[j] * r[i - j]; }\n            var k = -acc \/ e, neu = new Float64Array(a);\n            for (j = 1; j < i; j++) { neu[j] = a[j] + k * a[i - j]; }\n            neu[i] = k;\n            a = neu;\n            e *= (1 - k * k);\n            if (e <= 0) { break; }\n        }\n        return a;\n    }\n    function restfehler(x) {\n        var n = x.length, e = new Float32Array(n), p = LPC_ORDNUNG, B = LPC_BLOCK, w = new Float64Array(B), start, i, j;\n        for (i = 0; i < B; i++) { w[i] = 0.5 - 0.5 * Math.cos(2 * Math.PI * i \/ (B - 1)); }\n        for (start = 0; start < n; start += B) {\n            var ende = Math.min(n, start + B), m = ende - start, r = new Float64Array(p + 1), g = new Float64Array(m);\n            for (i = 0; i < m; i++) { g[i] = x[start + i] * w[i]; }\n            for (j = 0; j <= p; j++) { var s = 0; for (i = j; i < m; i++) { s += g[i] * g[i - j]; } r[j] = s; }\n            r[0] *= 1.000001;   \/\/ leichte Daempfung gegen schlecht bedingte Bloecke\n            var a = levinson(r, p);\n            for (i = start; i < ende; i++) {\n                var v = x[i];\n                for (j = 1; j <= p &#038;&#038; i - j >= 0; j++) { v += a[j] * x[i - j]; }\n                e[i] = v;\n            }\n        }\n        return e;\n    }<\/p>\n<p>    function blendeFuer(senkung) {\n        for (var i = 0; i < BLENDE.length; i++) { if (BLENDE[i][1] >= senkung) { return BLENDE[i][0]; } }\n        return null;   \/\/ mehr als 20 ms\n    }<\/p>\n<p>    \/\/ Sucht Stellen in einem Kanal auf dem Restfehler der Praediktion. Gibt [{i, spitze, umfeld,\n    \/\/ ueber, sprung, knick}] zurueck; spitze ist die Spitze oberhalb 5 kHz wie im Artikel.\n    function kanalSuchen(x, fs, stufen) {\n        var h = restfehler(x), hp = filtern(x, stufen), bl = Math.max(1, Math.round(BLOCK_S * fs)), nb = Math.floor(h.length \/ bl);\n        var spitze = new Float32Array(nb), ort = new Int32Array(nb), b, i;\n        for (b = 0; b < nb; b++) {\n            var m = 0, mi = b * bl;\n            for (i = b * bl; i < (b + 1) * bl; i++) { var a = h[i] < 0 ? -h[i] : h[i]; if (a > m) { m = a; mi = i; } }\n            spitze[b] = m; ort[b] = mi;\n        }\n        var um = Math.round(UMFELD_S \/ BLOCK_S), lu = Math.round(LUECKE_S \/ BLOCK_S), treffer = [];\n        var grenze = Math.pow(10, SCHWELLE_DB \/ 20), boden = Math.pow(10, BODEN_DBFS \/ 20);\n        for (b = um + lu; b < nb - um - lu; b++) {\n            var s = spitze[b];\n            if (s < boden) { continue; }\n            \/\/ nur lokale Maxima pruefen\n            if (s < spitze[b - 1] || s < spitze[b + 1]) { continue; }\n            var vor = median(spitze.subarray(b - lu - um, b - lu)), nach = median(spitze.subarray(b + lu + 1, b + lu + 1 + um));\n            var bezug = Math.max(vor, nach, 1e-9);\n            if (s < grenze * bezug) { continue; }\n            var kk = ort[b], e2 = 0, ne = 0;\n            for (i = kk - bl; i <= kk + bl; i++) {\n                if (i < 0 || i >= h.length || Math.abs(i - kk) <= 2) { continue; }\n                e2 += h[i] * h[i]; ne++;\n            }\n            var schaerfe = db(s \/ Math.sqrt(e2 \/ Math.max(1, ne) + 1e-30));\n            if (schaerfe < SCHAERFE_DB) { continue; }\n            \/\/ Sprung und Knick im Original um die Stelle (+-3 Werte um die Hochpass-Spitze)\n            var k = ort[b], sprung = 0, knick = 0, d1typ = [];\n            for (i = Math.max(2, k - 3); i <= Math.min(x.length - 1, k + 3); i++) {\n                sprung = Math.max(sprung, Math.abs(x[i] - x[i - 1]));\n                knick = Math.max(knick, Math.abs(x[i] - 2 * x[i - 1] + x[i - 2]));\n            }\n            for (i = Math.max(1, k - Math.round(0.005 * fs)); i < Math.min(x.length, k + Math.round(0.005 * fs)); i++) {\n                if (Math.abs(i - k) > 4) { d1typ.push(Math.abs(x[i] - x[i - 1])); }\n            }\n            var d1med = median(d1typ), hpSpitze = 0;\n            for (i = Math.max(0, k - bl); i < Math.min(hp.length, k + bl); i++) { hpSpitze = Math.max(hpSpitze, Math.abs(hp[i])); }\n            treffer.push({ i: k, spitze: hpSpitze, umfeld: bezug, ueber: db(s \/ bezug), schaerfe: schaerfe, sprung: sprung, knick: knick,\n                           art: sprung > 4 * d1med + 1e-7 ? 'sprung' : 'knick' });\n        }\n        return treffer;\n    }<\/p>\n<p>    \/\/ Sucht in allen Kanaelen, fasst Stellen innerhalb von 5 ms zusammen.\n    function suchen(kanaele, fs) {\n        var stufen = hochpass(fs, Math.min(HP_HZ, fs * 0.45)), alle = [];\n        kanaele.forEach(function (x, c) { kanalSuchen(x, fs, stufen).forEach(function (t) { t.kanal = c + 1; alle.push(t); }); });\n        alle.sort(function (a, b) { return a.i - b.i; });\n        var stellen = [], zs = Math.round(ZUSAMMEN_S * fs);\n        alle.forEach(function (t) {\n            var letzte = stellen[stellen.length - 1];\n            if (letzte && t.i - letzte.i <= zs) {\n                if (letzte.kanaele.indexOf(t.kanal) === -1) { letzte.kanaele.push(t.kanal); }\n                if (t.ueber > letzte.ueber) { var kn = letzte.kanaele; Object.keys(t).forEach(function (k) { letzte[k] = t[k]; }); letzte.kanaele = kn; }\n                return;\n            }\n            t.kanaele = [t.kanal];\n            stellen.push(t);\n        });\n        stellen.forEach(function (s) {\n            s.zeit_s = s.i \/ fs;\n            s.spitze_dbfs = db(s.spitze);\n            s.sprung_dbfs = db(s.sprung);\n            \/\/ Blende, die den Klick in der Messung um die Ueberhoehung plus 6 dB senkt\n            s.blende_ms = blendeFuer(s.ueber + 6);\n        });\n        \/\/ Anfang und Ende: Sprung aus der Stille bzw. in die Stille\n        var rand = [];\n        var n = kanaele[0].length, a0 = 0, e0 = 0;\n        kanaele.forEach(function (x) { a0 = Math.max(a0, Math.abs(x[0])); e0 = Math.max(e0, Math.abs(x[n - 1])); });\n        if (a0 >= 0.01) { rand.push({ art: 'anfang', zeit_s: 0, sprung_dbfs: db(a0) }); }\n        if (e0 >= 0.01) { rand.push({ art: 'ende', zeit_s: (n - 1) \/ fs, sprung_dbfs: db(e0) }); }\n        var gesamt = stellen.length;\n        stellen.sort(function (a, b) { return b.ueber - a.ueber; });\n        var gezeigt = stellen.slice(0, HOECHSTENS).sort(function (a, b) { return a.i - b.i; });\n        return { stellen: gezeigt, gesamt: gesamt, rand: rand, dauer_s: n \/ fs, fs: fs, kanaele: kanaele.length };\n    }\n    function str(u8, o, l) { var s = ''; for (var i = 0; i < l &#038;&#038; o + i < u8.length; i++) { s += String.fromCharCode(u8[o + i]); } return s; }\n    function le16(u8, o) { return u8[o] | (u8[o + 1] << 8); }\n    function le32(u8, o) { return (u8[o] | (u8[o + 1] << 8) | (u8[o + 2] << 16)) + ((u8[o + 3] << 24) >>> 0); }\n    function be16(u8, o) { return (u8[o] << 8) | u8[o + 1]; }\n    function be32(u8, o) { return ((u8[o] << 24) >>> 0) + (u8[o + 1] << 16) + (u8[o + 2] << 8) + u8[o + 3]; }\n\n    function pcm(puffer, start, laenge, anzahl, bytes, gleit, klein) {\n        var rahmen = Math.floor(laenge \/ (bytes * anzahl)), raus = [], c, i;\n        for (c = 0; c < anzahl; c++) { raus.push(new Float32Array(rahmen)); }\n        var dv = new DataView(puffer, start, rahmen * bytes * anzahl), u8 = new Uint8Array(puffer, start, rahmen * bytes * anzahl);\n        var teiler = Math.pow(2, bytes * 8 - 1), o = 0, v;\n        for (i = 0; i < rahmen; i++) {\n            for (c = 0; c < anzahl; c++) {\n                if (gleit) { v = bytes === 4 ? dv.getFloat32(o, klein) : dv.getFloat64(o, klein); }\n                else if (bytes === 1) { v = (klein ? u8[o] - 128 : dv.getInt8(o)) \/ teiler; }\n                else if (bytes === 2) { v = dv.getInt16(o, klein) \/ teiler; }\n                else if (bytes === 3) {\n                    var w = klein ? (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 \/ teiler;\n                } else { v = dv.getInt32(o, klein) \/ teiler; }\n                raus[c][i] = v;\n                o += bytes;\n            }\n        }\n        return raus;\n    }\n\n    function wavLesen(puffer) {\n        var u8 = new Uint8Array(puffer), p = 12, fmt = null, ds64 = null;\n        while (p + 8 <= u8.length) {\n            var id = str(u8, p, 4), l = le32(u8, p + 4);\n            if (id === 'ds64' &#038;&#038; l >= 16) { ds64 = le32(u8, p + 16) + le32(u8, p + 20) * 4294967296; }\n            if (id === 'fmt ') {\n                var tag = le16(u8, p + 8);\n                if (tag === 0xFFFE && l >= 26) { tag = le16(u8, p + 32); }\n                fmt = { tag: tag, anzahl: le16(u8, p + 10), rate: le32(u8, p + 12), bits: le16(u8, p + 22) };\n            } else if (id === 'data') {\n                if (l === 0xFFFFFFFF && ds64 !== null) { l = ds64; }\n                if (!fmt || (fmt.tag !== 1 && fmt.tag !== 3)) { return null; }\n                var bytes = fmt.bits \/ 8;\n                if ([1, 2, 3, 4, 8].indexOf(bytes) === -1 || !fmt.anzahl) { return null; }\n                return { format: 'WAV', rate: fmt.rate, kanaele: pcm(puffer, p + 8, Math.min(l, u8.length - p - 8), fmt.anzahl, bytes, fmt.tag === 3, true) };\n            }\n            if (l === 0xFFFFFFFF) { break; }\n            p += 8 + l + (l & 1);\n        }\n        return null;\n    }<\/p>\n<p>    function aiffLesen(puffer, aifc) {\n        var u8 = new Uint8Array(puffer), p = 12, comm = null;\n        while (p + 8 <= u8.length) {\n            var id = str(u8, p, 4), l = be32(u8, p + 4);\n            if (id === 'COMM') {\n                var exp = be16(u8, p + 16) &#038; 0x7FFF, hi = be32(u8, p + 18), lo = be32(u8, p + 22);\n                comm = { anzahl: be16(u8, p + 8), bits: be16(u8, p + 14), rate: Math.round((hi * 4294967296 + lo) * Math.pow(2, exp - 16383 - 63)),\n                         art: aifc &#038;&#038; l >= 22 ? str(u8, p + 26, 4) : 'NONE' };\n            } else if (id === 'SSND' && comm) {\n                var versatz = be32(u8, p + 8), start = p + 16 + versatz, laenge = Math.min(l - 8 - versatz, u8.length - start);\n                var gleit = \/^(fl32|FL32|fl64|FL64)$\/.test(comm.art);\n                if (!gleit && ['NONE', 'twos', 'sowt'].indexOf(comm.art) === -1) { return null; }\n                var bytes = gleit ? (comm.art.slice(2) === '32' ? 4 : 8) : Math.ceil(comm.bits \/ 8);\n                return { format: 'AIFF', rate: comm.rate, kanaele: pcm(puffer, start, laenge, comm.anzahl, bytes, gleit, comm.art === 'sowt') };\n            }\n            p += 8 + l + (l & 1);\n        }\n        return null;\n    }<\/p>\n<p>    \/\/ Abtastrate komprimierter Formate aus dem Kopf, damit der Decoder nicht umrechnet.\n    \/\/ Rate aus dem ersten Eintrag der Box stsd (AudioSampleEntry, 16.16 bei +32 ab Eintrag). Der Kopf kann am\n    \/\/ Dateiende stehen, deshalb wird die ganze Datei durchsucht. Raten ueber 65535 Hz passen nicht in das Feld.\n    function mp4Rate(puffer) {\n        var u = new Uint8Array(puffer), i;\n        for (i = 4; i + 46 < u.length; i++) {\n            if (u[i] === 0x73 &#038;&#038; u[i + 1] === 0x74 &#038;&#038; u[i + 2] === 0x73 &#038;&#038; u[i + 3] === 0x64) {\n                var r = (u[i + 44] << 8) | u[i + 45];\n                return r >= 3000 ? r : null;\n            }\n        }\n        return null;\n    }<\/p>\n<p>    function kopfRate(puffer) {\n        var u8 = new Uint8Array(puffer, 0, Math.min(puffer.byteLength, 1 << 20)), k = str(u8, 0, 4), p = 0;\n        if (k === 'ID3' &#038;&#038; u8.length > 10) {\n            p = 10 + ((u8[6] & 0x7f) << 21 | (u8[7] &#038; 0x7f) << 14 | (u8[8] &#038; 0x7f) << 7 | (u8[9] &#038; 0x7f));\n            if (str(u8, p, 4) === 'fLaC') { k = 'fLaC'; }\n        }\n        if (k === 'fLaC') { var s = p + 8; return { format: 'FLAC', rate: (u8[s + 10] << 12) | (u8[s + 11] << 4) | (u8[s + 12] >> 4) }; }\n        if (k === 'OggS' && u8.length > 64) {\n            var d = 27 + u8[26];\n            if (str(u8, d + 1, 6) === 'vorbis') { return { format: 'Ogg Vorbis', rate: le32(u8, d + 12) }; }\n            if (str(u8, d, 8) === 'OpusHead') { return { format: 'Opus', rate: 48000 }; }\n            return { format: 'Ogg', rate: null };\n        }\n        if (str(u8, 4, 4) === 'ftyp') { return { format: 'MP4', rate: mp4Rate(puffer) }; }\n        for (var j = p; j < Math.min(u8.length - 4, p + 65536); j++) {\n            if (u8[j] !== 0xFF || (u8[j + 1] &#038; 0xE0) !== 0xE0) { continue; }\n            var version = (u8[j + 1] >> 3) & 3, schicht = (u8[j + 1] >> 1) & 3, ri = (u8[j + 2] >> 2) & 3, bri = u8[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: 'MP3', rate: version === 3 ? basis : (version === 2 ? basis \/ 2 : basis \/ 4) };\n        }\n        return { format: null, rate: null };\n    }<\/p>\n<p>    \/\/ WAV und AIFF selbst lesen; null heisst: den Decoder des Browsers fragen.\n    function direktLesen(puffer) {\n        var u8 = new Uint8Array(puffer);\n        if (u8.length < 16) { return null; }\n        var k = str(u8, 0, 4), a = str(u8, 8, 4);\n        if ((k === 'RIFF' || k === 'RF64' || k === 'BW64') &#038;&#038; a === 'WAVE') { return wavLesen(puffer); }\n        if (k === 'FORM' &#038;&#038; (a === 'AIFF' || a === 'AIFC')) { return aiffLesen(puffer, a === 'AIFC'); }\n        return null;\n    }\n\n    \/\/ KERN-ENDE --------------------------------------------------------------\n\n\n    function el(id) { return document.getElementById(id); }\n    var NB = '\u00a0', ZEILEN = 100;\n    function satz(k, w) { return (T[k] || '').replace(\/\\{(\\w+)\\}\/g, function (_, x) { return w &#038;&#038; w[x] !== undefined ? w[x] : ''; }); }\n    function zahl(v, st) {\n        if (v === -Infinity) { return '\u2212\u221e'; }\n        if (v === null || v === undefined || !isFinite(v)) { return '\u2013'; }\n        var s = Number(v).toFixed(st === undefined ? 1 : st);\n        if (\/^-0(\\.0+)?$\/.test(s)) { s = s.slice(1); }\n        s = s.replace('-', '\u2212');\n        return T.dezimal === ',' ? s.replace('.', ',') : s;\n    }\n    function zeit(s) {\n        var m = Math.floor(s \/ 60), r = s - 60 * m;\n        return m + ':' + (r < 10 ? '0' : '') + zahl(r, 3);\n    }\n    function blendeText(ms) { return ms === null || ms === undefined ? T.blende_mehr : zahl(ms, ms < 1 ? 2 : 0) + NB + 'ms'; }\n\n    function zeilenDaten(r) {\n        var z = [];\n        r.rand.forEach(function (x) {\n            \/\/ Der Wert ist der erste bzw. letzte Abtastwert, kein Klick oberhalb 5 kHz: er gehoert zur Art.\n            z.push({ zeit: x.zeit_s, werte: [zeit(x.zeit_s), T['art_' + x.art] + ' (' + zahl(x.sprung_dbfs, 1) + NB + 'dBFS)', '\u2013', '\u2013', '\u2013', '\u2013'] });\n        });\n        r.stellen.forEach(function (s) {\n            z.push({ zeit: s.zeit_s, werte: [zeit(s.zeit_s), T['art_' + s.art], s.kanaele.join(', '), zahl(s.ueber, 1) + NB + 'dB',\n                zahl(s.spitze_dbfs, 1) + NB + 'dBFS', blendeText(s.blende_ms)] });\n        });\n        z.sort(function (a, b) { return a.zeit - b.zeit; });\n        return z;\n    }\n\n    function bericht(e) {\n        var r = e.r, z = [T.bericht_kopf + ': ' + (e.name || '')];\n        z.push(satz(r.gesamt + r.rand.length ? 'urteil_stellen' : 'urteil_sauber', { n: r.gesamt }));\n        zeilenDaten(r).forEach(function (x) { z.push(x.werte.join(' | ')); });\n        return z.join('\\n');\n    }\n\n    function kachel(name, wert) {\n        var d = document.createElement('div'); d.className = 'kf-kachel';\n        var a = document.createElement('div'); a.className = 'kf-kachel-name'; a.textContent = name;\n        var b = document.createElement('div'); b.className = 'kf-kachel-wert'; b.textContent = wert;\n        d.appendChild(a); d.appendChild(b);\n        return d;\n    }\n\n    function darstellen(e) {\n        var r = e.r;\n        if (!el('kf-kacheln')) { return e; }\n        var k = el('kf-kacheln'), spr = 0, kn = 0, max = null;\n        r.stellen.forEach(function (s) { if (s.art === 'sprung') { spr++; } else { kn++; } if (max === null || s.ueber > max) { max = s.ueber; } });\n        k.textContent = '';\n        k.appendChild(kachel(T.k_stellen, String(r.gesamt)));\n        k.appendChild(kachel(T.k_arten, spr + ' \/ ' + kn));\n        k.appendChild(kachel(T.k_max, max === null ? '\u2013' : zahl(max, 1) + NB + 'dB'));\n        k.appendChild(kachel(T.k_rand, r.rand.length ? String(r.rand.length) : T.wert_weich));\n        var u = el('kf-urteil'), stufe = r.gesamt + r.rand.length ? 'stellen' : 'sauber';\n        u.className = 'kf-urteil kf-u-' + stufe;\n        u.textContent = '';\n        var kopf = document.createElement('div'); kopf.className = 'kf-urteil-kopf';\n        kopf.textContent = satz('urteil_' + stufe, { n: r.gesamt });\n        var txt = document.createElement('div'); txt.className = 'kf-klein';\n        txt.textContent = T['urteil_text_' + stufe];\n        u.appendChild(kopf); u.appendChild(txt);\n        var t = el('kf-liste'), namen = [T.sp_zeit, T.sp_art, T.sp_kanal, T.sp_ueber, T.sp_klick, T.sp_blende], alle = zeilenDaten(r);\n        t.textContent = '';\n        alle.slice(0, ZEILEN).forEach(function (zeile) {\n            var tr = document.createElement('tr');\n            zeile.werte.forEach(function (w, i) {\n                var td = document.createElement('td');\n                if (i > 1) { td.className = 'kf-wert'; td.setAttribute('data-name', namen[i]); }\n                td.textContent = w; tr.appendChild(td);\n            });\n            t.appendChild(tr);\n        });\n        el('kf-mehr').textContent = alle.length > ZEILEN ? satz('mehr', { n: ZEILEN, gesamt: alle.length }) : (r.gesamt > r.stellen.length ? satz('mehr', { n: r.stellen.length, gesamt: r.gesamt }) : '');\n        el('kf-ausgabe').hidden = false;\n        el('kf-fehler').hidden = true;\n        return e;\n    }<\/p>\n<p>    function fehlerZeigen(s) {\n        var f = el('kf-fehler');\n        if (!f) { return; }\n        f.textContent = s; f.hidden = false;\n        el('kf-ausgabe').hidden = true;\n        el('kf-status').textContent = '';\n    }<\/p>\n<p>    function dekodieren(puffer, rate) {\n        return new Promise(function (ok, schlecht) {\n            var Ctx = window.OfflineAudioContext || window.webkitOfflineAudioContext;\n            if (!Ctx) { schlecht(new Error('webaudio')); return; }\n            var ctx, fertig = false;\n            try { ctx = new Ctx(1, 1, rate); } catch (x) { ctx = new Ctx(1, 1, 48000); }\n            function gut(b) { if (!fertig) { fertig = true; ok(b); } }\n            function nein(x) { if (!fertig) { fertig = true; schlecht(x || new Error('dekodieren')); } }\n            setTimeout(function () { nein(new Error('zeit')); }, 60000);\n            try { var p = ctx.decodeAudioData(puffer, gut, nein); if (p && p.then) { p.then(gut, nein); } } catch (x) { nein(x); }\n        });\n    }<\/p>\n<p>    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); } }<\/p>\n<p>    var letztes = null;\n    function verarbeiten(puffer, name) {\n        var info = null;\n        try { info = direktLesen(puffer); } catch (x) { info = null; }\n        var weg;\n        if (info) { weg = Promise.resolve(info); }\n        else {\n            var kopf = kopfRate(puffer);\n            if (!kopf.format) { fehlerZeigen(T.fehler_format); return Promise.resolve(null); }\n            if (el('kf-status')) { el('kf-status').textContent = T.status_dekodieren; }\n            var rate = kopf.rate && kopf.rate >= 3000 && kopf.rate <= 768000 ? kopf.rate : 48000;\n            weg = dekodieren(puffer.slice(0), rate).then(function (b) {\n                var k = [];\n                for (var c = 0; c < b.numberOfChannels; c++) { k.push(b.getChannelData(c)); }\n                return { format: kopf.format, rate: b.sampleRate, kanaele: k };\n            });\n        }\n        return weg.then(function (inf) {\n            if (!inf.kanaele.length || inf.kanaele[0].length < inf.rate * 0.05) { fehlerZeigen(T.fehler_kurz); return null; }\n            if (el('kf-status')) { el('kf-status').textContent = T.status_suchen; }\n            return new Promise(function (ok) {\n                spaeter(function () {\n                    var r = suchen(inf.kanaele, inf.rate);\n                    if (el('kf-status')) { el('kf-status').textContent = ''; }\n                    letztes = { name: name || '', r: r };\n                    ok(darstellen(letztes));\n                });\n            });\n        }, function () { fehlerZeigen(T.fehler_lesen); return null; });\n    }\n\n    function dateiNehmen(datei) {\n        if (!datei) { return; }\n        var leser = new FileReader();\n        el('kf-status').textContent = T.status_lesen;\n        leser.onload = function () { verarbeiten(leser.result, datei.name); };\n        leser.onerror = function () { fehlerZeigen(T.fehler_lesen); };\n        try { leser.readAsArrayBuffer(datei); } catch (x) { fehlerZeigen(T.fehler_lesen); }\n    }\n\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.KF = { T: T, suchen: suchen, direktLesen: direktLesen, blendeFuer: blendeFuer, verarbeiten: verarbeiten,\n        bericht: bericht, zeilenDaten: zeilenDaten, letztes: function () { return letztes; } };\n\n    var zone = el('kf-zone'), eingabe = el('kf-datei'), knopf = el('kf-knopf');\n    if (!zone || !eingabe || !knopf) { 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 (t) { zone.addEventListener(t, function (ev) { ev.preventDefault(); zone.classList.add('kf-ueber'); }); });\n    ['dragleave', 'drop'].forEach(function (t) { zone.addEventListener(t, function (ev) { ev.preventDefault(); zone.classList.remove('kf-ueber'); }); });\n    zone.addEventListener('drop', function (ev) { ev.preventDefault(); dateiNehmen(ev.dataTransfer &#038;&#038; ev.dataTransfer.files &#038;&#038; ev.dataTransfer.files[0]); });\n    var kop = el('kf-kopieren'), kopiert = el('kf-kopiert');\n    if (kop) {\n        kop.addEventListener('click', function () {\n            if (!letztes) { return; }\n            var t = bericht(letztes);\n            if (navigator.clipboard &#038;&#038; navigator.clipboard.writeText) {\n                navigator.clipboard.writeText(t).then(function () { kopiert.textContent = T.kopiert; }, function () { kopiert.textContent = T.kopieren_fehler; });\n            } else { kopiert.textContent = T.kopieren_fehler; }\n        });\n    }\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Searches an audio file for clicks, hard cuts and places where the waveform suddenly reverses, using the prediction error of a linear predictor. Lists each place with its time, how far it stands out, its level above 5\u00a0kHz and the fade length that buries it, based on a measurement of crossfades.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92901],"class_list":["post-20215","page","type-page","status-publish","hentry","tag-music-production"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Searches an audio file for clicks, hard cuts and places where the waveform suddenly reverses, using the prediction error of a linear predictor. Lists each place with its time, how far it stands out, its level above 5\u00a0kHz and the fade length that buries it, based on a measurement of crossfades.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-23T19:54: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=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/click-splice-finder-hard-cuts-slope-breaks\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/click-splice-finder-hard-cuts-slope-breaks\\\/\",\"name\":\"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-23T18:42:55+00:00\",\"dateModified\":\"2026-09-23T19:54:49+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/click-splice-finder-hard-cuts-slope-breaks\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/click-splice-finder-hard-cuts-slope-breaks\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/click-splice-finder-hard-cuts-slope-breaks\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file\"}]},{\"@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":"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/","og_locale":"en_US","og_type":"article","og_title":"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file - Lukas Wojcik - Blog","og_description":"Searches an audio file for clicks, hard cuts and places where the waveform suddenly reverses, using the prediction error of a linear predictor. Lists each place with its time, how far it stands out, its level above 5\u00a0kHz and the fade length that buries it, based on a measurement of crossfades.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-09-23T19:54: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":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/","name":"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-23T18:42:55+00:00","dateModified":"2026-09-23T19:54:49+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/click-splice-finder-hard-cuts-slope-breaks\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"Click and Splice Finder: find clicks, hard cuts and slope breaks in an audio file"}]},{"@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\/20215","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=20215"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/20215\/revisions"}],"predecessor-version":[{"id":20301,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/20215\/revisions\/20301"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=20215"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=20215"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}