{"id":20240,"date":"2026-09-23T21:37:48","date_gmt":"2026-09-23T19:37:48","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/time-offset-polarity-checker-two-tracks\/"},"modified":"2026-09-23T21:59:40","modified_gmt":"2026-09-23T19:59:40","slug":"time-offset-polarity-checker-two-tracks","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/","title":{"rendered":"Offset and Polarity Checker: time offset and reversed polarity of two tracks"},"content":{"rendered":"<div class=\"lw-vp\" id=\"vp-wurzel\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<style>\n.lw-vp { --vp-warn: #e8a33d; }\n:root[data-theme=\"light\"] .lw-vp { --vp-warn: #8f5300; }\n.lw-vp .vp-zonen { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }\n.lw-vp .vp-zone { border: 2px dashed var(--border, #2a2a35); border-radius: 8px; padding: 18px; text-align: center; min-width: 0; }\n.lw-vp .vp-zone.vp-ueber { border-color: var(--accent, #2ba6df); }\n.lw-vp .vp-zone-name { margin: 0 0 10px; color: var(--text-primary, #e8e8ee); font-weight: 700; }\n.lw-vp .vp-datei-name { font-family: monospace; font-size: 0.84rem; color: var(--text-secondary, #a0a0b0); margin: 10px 0 0; overflow-wrap: anywhere; min-height: 1.2em; }\n.lw-vp .vp-waehlen { background: transparent; color: var(--text-primary, #e8e8ee); border: 1px solid var(--border, #2a2a35); padding: 9px 16px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; }\n.lw-vp .vp-kacheln { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 4px 0; }\n.lw-vp .vp-kachel { background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 11px 14px; min-width: 0; }\n.lw-vp .vp-kachel-name { font-size: 0.74rem; color: var(--text-secondary, #a0a0b0); text-transform: uppercase; letter-spacing: 0.4px; }\n.lw-vp .vp-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-vp .vp-urteil { border-radius: 8px; padding: 12px 15px; margin-top: 14px; border: 1px solid var(--border, #2a2a35); background: var(--bg-body, #14141a); }\n.lw-vp .vp-urteil-kopf { font-weight: 700; color: var(--text-primary, #e8e8ee); margin-bottom: 4px; }\n.lw-vp .vp-u-gut .vp-urteil-kopf { color: var(--ok, #7ee787); }\n.lw-vp .vp-u-tun .vp-urteil-kopf { color: var(--vp-warn); }\n.lw-vp .vp-urteil p { margin: 6px 0 0; }\n.lw-vp .vp-befehl { font-family: monospace; font-size: 0.84rem; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 10px 12px; margin-top: 10px; overflow-wrap: anywhere; color: var(--text-primary, #e8e8ee); }\n.lw-vp .vp-tabelle { overflow-x: auto; margin-top: 6px; }\n.lw-vp table { border-collapse: collapse; width: 100%; font-size: 0.86rem; }\n.lw-vp th, .lw-vp td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border, #2a2a35); vertical-align: top; }\n.lw-vp th { color: var(--text-primary, #e8e8ee); }\n.lw-vp td { color: var(--text-primary, #e8e8ee); }\n.lw-vp td.vp-wert { font-family: monospace; color: var(--text-secondary, #a0a0b0); white-space: nowrap; }\n.lw-vp .vp-h { font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 24px 0 8px; }\n.lw-vp .vp-klein { font-size: 0.84rem; color: var(--text-secondary, #a0a0b0); line-height: 1.55; }\n.lw-vp .vp-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-vp .vp-fehler { color: var(--fehler, #f0857a); font-size: 0.88rem; margin-top: 12px; }\n.lw-vp a { color: var(--accent, #2ba6df); }\n@media (max-width: 600px) {\n    .lw-vp .vp-zonen { grid-template-columns: 1fr; }\n    .lw-vp .vp-kacheln { grid-template-columns: 1fr 1fr; gap: 8px; }\n    .lw-vp .vp-kachel-wert { font-size: 1.15rem; }\n    .lw-vp table.vp-karten thead { display: none; }\n    .lw-vp table.vp-karten tr { display: block; padding: 8px 0; border-bottom: 1px solid var(--border, #2a2a35); }\n    .lw-vp table.vp-karten td { display: block; border: none; padding: 1px 0; }\n    .lw-vp table.vp-karten td.vp-wert { white-space: normal; overflow-wrap: anywhere; }\n    .lw-vp table.vp-karten td:first-child { font-weight: 700; }\n    .lw-vp table.vp-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;\">Measures how far a second track is shifted against a first one and whether its polarity is reversed: two microphones on one source, DI and amp of a bass, a track before and after a plugin with latency. It also shows what the sum of both tracks gains or loses, overall and below 150\u00a0Hz, including for kick and bass, which share no common source. The files are read in the browser and not uploaded.<\/p>\n<div class=\"vp-zonen\">\n<div id=\"vp-zone-a\" class=\"vp-zone\">\n<p class=\"vp-zone-name\">Track A (reference)<\/p>\n<p>            <button id=\"vp-knopf-a\" type=\"button\" class=\"vp-waehlen\">Select file<\/button><br \/>\n            <input id=\"vp-datei-a\" type=\"file\" accept=\"audio\/*,.wav,.wave,.aif,.aiff,.aifc,.flac,.mp3,.m4a,.ogg,.opus\" hidden><\/p>\n<p id=\"vp-name-a\" class=\"vp-datei-name\" data-mp-mask>\n<\/p><\/div>\n<div id=\"vp-zone-b\" class=\"vp-zone\">\n<p class=\"vp-zone-name\">Track B (gets shifted)<\/p>\n<p>            <button id=\"vp-knopf-b\" type=\"button\" class=\"vp-waehlen\">Select file<\/button><br \/>\n            <input id=\"vp-datei-b\" type=\"file\" accept=\"audio\/*,.wav,.wave,.aif,.aiff,.aifc,.flac,.mp3,.m4a,.ogg,.opus\" hidden><\/p>\n<p id=\"vp-name-b\" class=\"vp-datei-name\" data-mp-mask>\n<\/p><\/div>\n<\/p><\/div>\n<p class=\"vp-klein\" style=\"margin: 12px 0 0;\">With only track A and a stereo file, left is measured against right. WAV and AIFF are read sample by sample; FLAC, MP3, M4A, Ogg and Opus go through the browser&#039;s decoder.<\/p>\n<p>    <button id=\"vp-messen\" type=\"button\" class=\"button\" disabled style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 11px 22px; border-radius: 6px; font-weight: 700; cursor: pointer; margin-top: 14px;\">Measure<\/button><br \/>\n    <button id=\"vp-leeren\" type=\"button\" class=\"vp-knopf\" style=\"margin-left: 8px;\">Clear selection<\/button><\/p>\n<div id=\"vp-status\" class=\"vp-klein\" data-mp-mask style=\"margin-top: 12px;\" aria-live=\"polite\"><\/div>\n<div id=\"vp-fehler\" class=\"vp-fehler\" data-mp-mask hidden><\/div>\n<div id=\"vp-ausgabe\" data-mp-mask style=\"margin-top: 18px;\" aria-live=\"polite\" hidden>\n<div id=\"vp-kacheln\" class=\"vp-kacheln\"><\/div>\n<div id=\"vp-urteil\" class=\"vp-urteil\"><\/div>\n<div class=\"vp-h\">Sum of both tracks<\/div>\n<div class=\"vp-tabelle\">\n<table class=\"vp-karten\">\n<thead>\n<tr>\n<th>Case<\/th>\n<th>Sum, full band<\/th>\n<th>Below 150\u00a0Hz<\/th>\n<th>Weakest 100\u00a0ms window below 150\u00a0Hz<\/th>\n<\/tr>\n<\/thead>\n<tbody id=\"vp-summen\"><\/tbody>\n<\/table><\/div>\n<p id=\"vp-summe-hinweis\" class=\"vp-klein\" style=\"margin: 8px 0 0;\">\n<p>        <button id=\"vp-kopieren\" type=\"button\" class=\"vp-knopf\">Copy result<\/button><br \/>\n        <span id=\"vp-kopiert\" class=\"vp-klein\" style=\"margin-left: 10px;\"><\/span>\n    <\/div>\n<div class=\"vp-h\">How it measures<\/div>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">From up to 30\u00a0s of common material, starting 0.1\u00a0s after the beginning, the cross-correlation of both tracks is computed for shifts of up to \u00b1100\u00a0ms. The position of the peak comes from a whitened version (SCOT) in which every frequency counts equally, so low tones and reverb do not smear the peak. Values between samples come from sinc interpolation to a thousandth of a sample. Match and polarity come from the ordinary normalized correlation at that position; it weights by energy, the way the sum is heard.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">The table computes the sum of both tracks as they are, with the polarity of B reversed and after the shift. Below 150\u00a0Hz, a fourth-order Butterworth low-pass filters both tracks the same way. The weakest 100\u00a0ms window only counts places where both tracks play (each no more than 20\u00a0dB below its loudest window); with kick and bass, that is the moment in which the low end cancels most.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">Reversing the polarity inverts every sample and acts on all frequencies alike. An offset, by contrast, turns the phase differently for each frequency: at 1\u00a0ms the first cancellation lies at 500\u00a0Hz, the next at 1.5\u00a0kHz. That is why reversed polarity does not replace a shift. Sound travels 344 m\/s in air at 20 \u00b0C, so 1\u00a0ms corresponds to 34.4 cm of distance between two microphones.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">In FL Studio, Fruity Stereo Shaper reverses the polarity of one or both channels and delays one channel against the other; according to the manual, the delay compensation of a mixer track can be set by hand in milliseconds or in samples, samples being the finest setting.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">Tested on 21 cases with a known offset: whole and fractional offsets were hit to within 0.016 samples, two microphones with room sound and noise (r = 0.32) to within 0.011 samples, and a snare top and bottom with a high-pass on the bottom microphone was recognized as shifted and inverted. A sine tone remained ambiguous; kick against bass, drums against bass and independent noise gave no common signal. After the suggested ffmpeg command, the tracks were aligned in all seven cases measured again, and the sum matched the prediction to within 0.1\u00a0dB. In a real browser (Chromium), the same track B as FLAC, MP3, AAC in M4A and Opus against the WAV version of A gave an offset of 64 samples each, to within 0.002 samples: the decoder removes the encoder delay, and Opus is brought to the rate of A.<\/p>\n<div class=\"vp-h\">Articles on this<\/div>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">How sidechain compression lowers the bass on every kick instead of shifting the two against each other is shown in the tutorial \u201c<a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/music-production\/tutorials-en-music-production\/sidechain-compression-fl-studio-2026-step-by-step\/\">Sidechain Compression in FL Studio 2026<\/a>\u201d.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">Comb filter, snare top and bottom and kick in and out are measured in the article \u201c<span class=\"lw-artikel-geplant\">Offset and Polarity of Two Tracks<\/span>\u201d.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">What start phase, tuning, high-pass and ducking change in the low-end sum of kick and bass is measured in the article \u201c<span class=\"lw-artikel-geplant\">Kick and Bass in the Low End<\/span>\u201d.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">What a delay between left and right costs in the mono sum is measured in the article \u201c<span class=\"lw-artikel-geplant\">Stereo Widening<\/span>\u201d.<\/p>\n<p class=\"vp-klein\" style=\"margin: 0 0 6px;\">How much latency a linear-phase EQ brings along, a frequent cause of offsets in parallel paths, is measured in the article \u201c<span class=\"lw-artikel-geplant\">Linear Phase Versus Minimum Phase EQ<\/span>\u201d.<\/p>\n<p class=\"vp-klein\" style=\"margin: 20px 0 0;\">Sources:<br \/>\n        <a href=\"https:\/\/www.image-line.com\/fl-studio-learning\/fl-studio-online-manual\/html\/mixer_trackprops.htm\" rel=\"noopener\">Image-Line: Mixer Track Effects (Plugin Delay Compensation)<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/www.image-line.com\/fl-studio-learning\/fl-studio-online-manual\/html\/plugins\/Fruity%20Stereo%20Shaper.htm\" rel=\"noopener\">Image-Line: Fruity Stereo Shaper<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/ffmpeg.org\/ffmpeg-filters.html\" rel=\"noopener\">ffmpeg Filters Documentation (adelay, atrim, aeval)<\/a> \u00b7<br \/>\n        <a href=\"https:\/\/hyperphysics.gsu.edu\/hbase\/Sound\/souspe.html\" rel=\"noopener\">HyperPhysics: Speed of Sound<\/a>\n    <\/p>\n<p class=\"vp-klein\" style=\"font-size: 0.8rem; margin: 14px 0 0;\">The search only covers \u00b1100\u00a0ms; a larger offset gives \u201cno common signal\u201d. An offset that drifts over the file, for example with different clocks in two devices, has no fixed position; the result is then an average over the first 30\u00a0s. Filters delay by themselves: a low-pass at 5\u00a0kHz on track B moved the measured position by 3.9 samples in the test, and the shift compensates exactly this delay as well.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';\n    const T = {\"dezimal\":\".\",\"einleitung\":\"Measures how far a second track is shifted against a first one and whether its polarity is reversed: two microphones on one source, DI and amp of a bass, a track before and after a plugin with latency. It also shows what the sum of both tracks gains or loses, overall and below 150\u00a0Hz, including for kick and bass, which share no common source. The files are read in the browser and not uploaded.\",\"zone_a\":\"Track A (reference)\",\"zone_b\":\"Track B (gets shifted)\",\"knopf_datei\":\"Select file\",\"zone_hinweis\":\"With only track A and a stereo file, left is measured against right. WAV and AIFF are read sample by sample; FLAC, MP3, M4A, Ogg and Opus go through the browser\\u0027s decoder.\",\"knopf_messen\":\"Measure\",\"knopf_leeren\":\"Clear selection\",\"status_lesen\":\"Reading the files \u2026\",\"status_messen\":\"Measuring \u2026\",\"k_versatz\":\"Offset\",\"k_werte\":\"In samples\",\"k_polaritaet\":\"Polarity\",\"k_r\":\"Match r\",\"werte\":\"samples\",\"pol_gleich\":\"same\",\"pol_gedreht\":\"reversed\",\"pol_unklar\":\"unclear\",\"spaeter\":\"B is {ms} later than A\",\"frueher\":\"B is {ms} earlier than A\",\"urteil_deckend\":\"Aligned\",\"urteil_text_deckend\":\"B sits under A to within half a sample and has the same polarity. There is nothing to shift.\",\"urteil_versatz\":\"{lage}\",\"urteil_text_versatz\":\"Shifted by {werte} samples, the tracks line up. The command below shifts B with ffmpeg; in a DAW the track can be moved by the same number of samples.\",\"urteil_verpolt\":\"B has reversed polarity\",\"urteil_text_verpolt\":\"The tracks are aligned in time, but B is inverted: where A swings up, B swings down. Reversing the polarity of B restores the sum.\",\"urteil_versatz_verpolt\":\"{lage}, with reversed polarity\",\"urteil_text_versatz_verpolt\":\"Shifted by {werte} samples and with reversed polarity, the tracks line up. The command below does both.\",\"urteil_mehrdeutig\":\"Offset not unambiguous\",\"urteil_text_mehrdeutig\":\"Several shifts fit almost equally well. This happens with sustained tones whose waveform repeats with every period; a passage with attacks or noise settles the question.\",\"kandidaten\":\"Candidates: {liste}.\",\"urteil_kein_bezug\":\"No common signal\",\"urteil_text_kein_bezug\":\"The tracks share no waveform, as with kick and bass or two different instruments. An offset cannot be measured then. For the sum, the low end is what counts: the table shows what it loses below 150\u00a0Hz as is and with the polarity of B reversed.\",\"hinweis_schwach\":\"The waveforms match only in part (r = {r}), for example because of room sound, filters or distortion. The position comes from the whitened correlation and is not affected by this; the sum gains correspondingly less from the shift.\",\"hinweis_unklar\":\"The waveform does not decide whether B is inverted here (r = {r}): filters shift low frequencies differently from high ones. The table shows which polarity gives the louder sum.\",\"hinweis_kerbe\":\"As long as the offset remains, the sum acts as a comb filter with its first notch at {hz} and further notches at every odd multiple of it. {ms} correspond to {cm} of sound travel in air.\",\"hinweis_dekodiert\":\"At least one file went through the browser\u2019s decoder and was decoded at {hz}, the rate of the comparison. MP3 and AAC encoder delay is removed by the decoder when the file states it; the offset then refers to the audio itself.\",\"hinweis_lr\":\"Measured: left against right channel of the same file; B is the right channel.\",\"befehl_hinweis\":\"The command takes B as b.wav and writes b_korrigiert.wav; atrim moves it earlier, adelay later, aeval reverses the polarity.\",\"h_summe\":\"Sum of both tracks\",\"sp_fall\":\"Case\",\"sp_gesamt\":\"Sum, full band\",\"sp_tief\":\"Below 150\u00a0Hz\",\"sp_fenster\":\"Weakest 100\u00a0ms window below 150\u00a0Hz\",\"fall_jetzt\":\"as they are\",\"fall_gedreht\":\"B reversed\",\"fall_verschoben\":\"B shifted by {werte} samples\",\"fall_verschoben_gedreht\":\"B shifted and reversed\",\"kaum_tief\":\"hardly any low end\",\"summe_hinweis\":\"Level of the sum against the sum of the individual powers: +3\u00a0dB means identical and aligned, 0\u00a0dB behaves like two independent signals, negative values are cancellation. Measured over {s}.\",\"knopf_kopieren\":\"Copy result\",\"kopiert\":\"copied\",\"kopieren_fehler\":\"copying not possible\",\"bericht_kopf\":\"Offset and polarity\",\"fehler_format\":\"One of the files is not in a format read here: WAV, AIFF, FLAC, MP3, M4A, Ogg or Opus.\",\"fehler_lesen\":\"One of the files could not be read or decoded.\",\"fehler_kurz\":\"Too little common material: at least 0.25\u00a0s is needed.\",\"fehler_mono\":\"Only track A is selected, and it is mono. Left against right needs a stereo file; otherwise a second file goes in as track B.\",\"fehler_rate\":\"A and B have different sample rates ({a} and {b}\u00a0Hz). Converted to one common rate, they can be compared; otherwise the offset drifts over the length of the file.\",\"h_erklaerung\":\"How it measures\",\"erkl_verfahren\":\"From up to 30\u00a0s of common material, starting 0.1\u00a0s after the beginning, the cross-correlation of both tracks is computed for shifts of up to \u00b1100\u00a0ms. The position of the peak comes from a whitened version (SCOT) in which every frequency counts equally, so low tones and reverb do not smear the peak. Values between samples come from sinc interpolation to a thousandth of a sample. Match and polarity come from the ordinary normalized correlation at that position; it weights by energy, the way the sum is heard.\",\"erkl_summe\":\"The table computes the sum of both tracks as they are, with the polarity of B reversed and after the shift. Below 150\u00a0Hz, a fourth-order Butterworth low-pass filters both tracks the same way. The weakest 100\u00a0ms window only counts places where both tracks play (each no more than 20\u00a0dB below its loudest window); with kick and bass, that is the moment in which the low end cancels most.\",\"erkl_polaritaet\":\"Reversing the polarity inverts every sample and acts on all frequencies alike. An offset, by contrast, turns the phase differently for each frequency: at 1\u00a0ms the first cancellation lies at 500\u00a0Hz, the next at 1.5\u00a0kHz. That is why reversed polarity does not replace a shift. Sound travels 344 m\\\/s in air at 20 \u00b0C, so 1\u00a0ms corresponds to 34.4 cm of distance between two microphones.\",\"erkl_daw\":\"In FL Studio, Fruity Stereo Shaper reverses the polarity of one or both channels and delays one channel against the other; according to the manual, the delay compensation of a mixer track can be set by hand in milliseconds or in samples, samples being the finest setting.\",\"erkl_messung\":\"Tested on 21 cases with a known offset: whole and fractional offsets were hit to within 0.016 samples, two microphones with room sound and noise (r = 0.32) to within 0.011 samples, and a snare top and bottom with a high-pass on the bottom microphone was recognized as shifted and inverted. A sine tone remained ambiguous; kick against bass, drums against bass and independent noise gave no common signal. After the suggested ffmpeg command, the tracks were aligned in all seven cases measured again, and the sum matched the prediction to within 0.1\u00a0dB. In a real browser (Chromium), the same track B as FLAC, MP3, AAC in M4A and Opus against the WAV version of A gave an offset of 64 samples each, to within 0.002 samples: the decoder removes the encoder delay, and Opus is brought to the rate of A.\",\"h_artikel\":\"Articles on this\",\"art_sidechain_vor\":\"How sidechain compression lowers the bass on every kick instead of shifting the two against each other is shown in the tutorial \u201c\",\"art_sidechain_titel\":\"Sidechain Compression in FL Studio 2026\",\"art_sidechain_nach\":\"\u201d.\",\"art_versatz_vor\":\"Comb filter, snare top and bottom and kick in and out are measured in the article \u201c\",\"art_versatz_titel\":\"Offset and Polarity of Two Tracks\",\"art_versatz_nach\":\"\u201d.\",\"art_kickbass_vor\":\"What start phase, tuning, high-pass and ducking change in the low-end sum of kick and bass is measured in the article \u201c\",\"art_kickbass_titel\":\"Kick and Bass in the Low End\",\"art_kickbass_nach\":\"\u201d.\",\"art_stereo_vor\":\"What a delay between left and right costs in the mono sum is measured in the article \u201c\",\"art_stereo_titel\":\"Stereo Widening\",\"art_stereo_nach\":\"\u201d.\",\"art_eq_vor\":\"How much latency a linear-phase EQ brings along, a frequent cause of offsets in parallel paths, is measured in the article \u201c\",\"art_eq_titel\":\"Linear Phase Versus Minimum Phase EQ\",\"art_eq_nach\":\"\u201d.\",\"quellen_titel\":\"Sources\",\"grenze\":\"The search only covers \u00b1100\u00a0ms; a larger offset gives \u201cno common signal\u201d. An offset that drifts over the file, for example with different clocks in two devices, has no fixed position; the result is then an average over the first 30\u00a0s. Filters delay by themselves: a low-pass at 5\u00a0kHz on track B moved the measured position by 3.9 samples in the test, and the shift compensates exactly this delay as well.\"};<\/p>\n<p>    \/\/ KERN-ANFANG ------------------------------------------------------------\n    \/\/ Versatz- und Polaritaetspruefer: Kreuzkorrelation zweier Spuren (oder links gegen rechts)\n    \/\/ in +-100 ms, dazu die Summe der beiden Spuren, gesamt und unter 150 Hz.\n    var MAX_VERSATZ_S = 0.1;       \/\/ gesuchter Bereich je Richtung\n    var ANALYSE_S = 30;            \/\/ hoechstens so viel Material geht in die Korrelation\n    var BLOCK = 65536, FFT_N = 131072;\n    var Q_KLAR = 5;                \/\/ geweisster Gipfel mindestens fuenfmal so hoch wie alles ausserhalb +-0,5 ms\n                                   \/\/ (Testdateien: gemeinsame Quelle 12 bis 74, Kick gegen Bass 2,2, Rauschen 1,0)\n    var R_MIN = 0.3;               \/\/ ohne klaren geweissten Gipfel: darunter kein gemeinsames Signal\n    var R_SCHWACH = 0.6;           \/\/ darunter nur teilweise gemeinsames Material\n    var R_POL = 0.2;               \/\/ darunter entscheidet die Wellenform die Polaritaet nicht\n    var MEHRDEUTIG = 0.9;          \/\/ ein zweiter Gipfel ab 90 % des ersten macht die Lage mehrdeutig\n    var TIEF_HZ = 150;\n    var FENSTER_S = 0.1, AKTIV_DB = 20;\n    var SCHALL_M_S = 344;          \/\/ 20 Grad Celsius, HyperPhysics<\/p>\n<p>    function db10(v) { return v > 0 ? 10 * Math.log10(v) : -Infinity; }<\/p>\n<p>    \/\/ Komplexe FFT, radix 2, an Ort und Stelle\n    function fft(re, im, invers) {\n        var n = re.length, i, j = 0, k, m, t;\n        for (i = 1; i < n; i++) {\n            k = n >> 1;\n            while (j & k) { j ^= k; k >>= 1; }\n            j |= k;\n            if (i < j) { t = re[i]; re[i] = re[j]; re[j] = t; t = im[i]; im[i] = im[j]; im[j] = t; }\n        }\n        for (m = 2; m <= n; m <<= 1) {\n            var w = (invers ? 2 : -2) * Math.PI \/ m, wr = Math.cos(w), wi = Math.sin(w), h = m >> 1;\n            for (i = 0; i < n; i += m) {\n                var cr = 1, ci = 0;\n                for (k = 0; k < h; k++) {\n                    var a = i + k, b = a + h, xr = re[b] * cr - im[b] * ci, xi = re[b] * ci + im[b] * cr;\n                    re[b] = re[a] - xr; im[b] = im[a] - xi; re[a] += xr; im[a] += xi;\n                    t = cr * wr - ci * wi; ci = cr * wi + ci * wr; cr = t;\n                }\n            }\n        }\n        if (invers) { for (i = 0; i < n; i++) { re[i] \/= n; im[i] \/= n; } }\n    }\n\n    function mono(kanaele) {\n        if (kanaele.length === 1) { return kanaele[0]; }\n        var n = kanaele[0].length, y = new Float32Array(n), c, i;\n        for (c = 0; c < kanaele.length; c++) { for (i = 0; i < n; i++) { y[i] += kanaele[c][i] \/ kanaele.length; } }\n        return y;\n    }\n\n    \/\/ Butterworth-Tiefpass 4. Ordnung als zwei Biquads (RBJ); gleiche Filter auf beiden Spuren\n    function tiefpass(x, fs, f0) {\n        var y = new Float32Array(x.length), i;\n        for (i = 0; i < x.length; i++) { y[i] = x[i]; }\n        [0.5411961, 1.3065630].forEach(function (q) {\n            var w = 2 * Math.PI * f0 \/ fs, c = Math.cos(w), al = Math.sin(w) \/ (2 * q), a0 = 1 + al;\n            var b0 = (1 - c) \/ 2 \/ a0, b1 = (1 - c) \/ a0, b2 = b0, a1 = -2 * c \/ a0, a2 = (1 - al) \/ a0;\n            var x1 = 0, x2 = 0, y1 = 0, y2 = 0;\n            for (i = 0; i < y.length; i++) {\n                var v = y[i], o = b0 * v + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;\n                x2 = x1; x1 = v; y2 = y1; y1 = o; y[i] = o;\n            }\n        });\n        return y;\n    }\n\n    \/\/ Kreuzspektrum ueber Bloecke summiert. Daraus zwei Funktionen ueber k in [-L, L]:\n    \/\/ r(k) = Summe a[n] b[n+k] \/ Wurzel(Ea * Eb(k)), die gewoehnliche normierte Korrelation, und\n    \/\/ w(k), dieselbe Korrelation mit geweisstem Spektrum (SCOT: Kreuzspektrum durch die Wurzel der\n    \/\/ beiden Leistungsspektren). w hat einen schmalen Gipfel auch dort, wo tiefe Toene und Nachhall\n    \/\/ r verschmieren. Positives k: b kommt k Werte spaeter als a; Index j = k + L.\n    function korrelation(a, b, fs) {\n        var L = Math.round(MAX_VERSATZ_S * fs), n = Math.min(a.length, b.length);\n        var anf = L, ende = Math.min(n - L, L + Math.round(ANALYSE_S * fs));\n        if (ende - anf < Math.round(0.05 * fs)) { return null; }\n        var re = new Float64Array(FFT_N), im = new Float64Array(FFT_N), re2 = new Float64Array(FFT_N), im2 = new Float64Array(FFT_N);\n        var gr = new Float64Array(FFT_N), gi = new Float64Array(FFT_N), paa = new Float64Array(FFT_N), pbb = new Float64Array(FFT_N), s, i, j;\n        for (s = anf; s < ende; s += BLOCK) {\n            var m = Math.min(BLOCK, ende - s);\n            re.fill(0); im.fill(0); re2.fill(0); im2.fill(0);\n            for (i = 0; i < m; i++) { re[i] = a[s + i]; }\n            for (i = 0; i < m + 2 * L; i++) { re2[i] = b[s - L + i]; }\n            fft(re, im, false); fft(re2, im2, false);\n            for (i = 0; i < FFT_N; i++) {       \/\/ konj(A) * B\n                gr[i] += re[i] * re2[i] + im[i] * im2[i]; gi[i] += re[i] * im2[i] - im[i] * re2[i];\n                paa[i] += re[i] * re[i] + im[i] * im[i]; pbb[i] += re2[i] * re2[i] + im2[i] * im2[i];\n            }\n        }\n        var nenner = new Float64Array(FFT_N), hoechst = 0;\n        for (i = 0; i < FFT_N; i++) { nenner[i] = Math.sqrt(paa[i] * pbb[i]); if (nenner[i] > hoechst) { hoechst = nenner[i]; } }\n        for (i = 0; i < FFT_N; i++) {\n            var d = nenner[i] + 1e-6 * hoechst;\n            re[i] = gr[i]; im[i] = gi[i]; re2[i] = gr[i] \/ d; im2[i] = gi[i] \/ d;\n        }\n        fft(re, im, true); fft(re2, im2, true);\n        var ea = 0, kum = new Float64Array(n + 1), r = new Float64Array(2 * L + 1), w = new Float64Array(2 * L + 1);\n        for (i = anf; i < ende; i++) { ea += a[i] * a[i]; }\n        for (i = 0; i < n; i++) { kum[i + 1] = kum[i] + b[i] * b[i]; }\n        for (j = 0; j <= 2 * L; j++) {\n            var eb = kum[ende - L + j] - kum[anf - L + j], nn = Math.sqrt(ea * eb);\n            r[j] = nn > 0 ? re[j] \/ nn : 0;\n            w[j] = re2[j];\n        }\n        return { r: r, w: w, L: L, anf: anf, ende: ende };\n    }<\/p>\n<p>    \/\/ Wert einer abgetasteten Funktion zwischen den Stuetzstellen (Lanczos-gefensterter Sinc, +-16)\n    function zwischen(f, t) {\n        var i0 = Math.floor(t), s = 0, j;\n        for (j = i0 - 15; j <= i0 + 16; j++) {\n            if (j < 0 || j >= f.length) { continue; }\n            var x = t - j;\n            s += f[j] * (x === 0 ? 1 : Math.sin(Math.PI * x) \/ (Math.PI * x) * Math.sin(Math.PI * x \/ 16) \/ (Math.PI * x \/ 16));\n        }\n        return s;\n    }\n    \/\/ Lage des Gipfels von |f| um die Stuetzstelle i, auf ein Tausendstel Wert genau\n    function fein(f, i) {\n        var best = i, bw = Math.abs(f[i]);\n        [0.05, 0.005, 0.0005].forEach(function (d) {\n            var mitte = best, t;\n            for (t = mitte - 20 * d; t <= mitte + 20 * d + d \/ 2; t += d) {\n                var v = Math.abs(zwischen(f, t));\n                if (v > bw) { bw = v; best = t; }\n            }\n        });\n        return best;\n    }\n    function hoechstesBei(f) {\n        var best = 0, i;\n        for (i = 1; i < f.length; i++) { if (Math.abs(f[i]) > Math.abs(f[best])) { best = i; } }\n        return best;\n    }<\/p>\n<p>    \/\/ Weitere Gipfel von |r| ab 90 % des Hauptgipfels, deren Tal zum Hauptgipfel unter die Haelfte faellt\n    function nebengipfel(r, best, L) {\n        var andere = [], i, k;\n        for (i = 1; i < r.length - 1; i++) {\n            var v = Math.abs(r[i]);\n            if (i === best || v < Math.abs(r[i - 1]) || v < Math.abs(r[i + 1]) || v < MEHRDEUTIG * Math.abs(r[best])) { continue; }\n            var tal = Infinity, a = Math.min(i, best), b = Math.max(i, best);\n            for (k = a; k <= b; k++) { tal = Math.min(tal, Math.abs(r[k])); }\n            if (tal < 0.5 * Math.abs(r[best])) { andere.push({ k: i - L, r: r[i] }); }\n        }\n        return andere.sort(function (x, y) { return Math.abs(y.r) - Math.abs(x.r); });\n    }\n\n    \/\/ Klarheit des geweissten Gipfels: Hoehe gegen den hoechsten Wert ausserhalb +-0,5 ms\n    function klarheit(w, j, fs) {\n        var rand = Math.max(3, Math.round(0.0005 * fs)), zweit = 0, i;\n        for (i = 0; i < w.length; i++) { if (Math.abs(i - j) > rand && Math.abs(w[i]) > zweit) { zweit = Math.abs(w[i]); } }\n        return zweit > 0 ? Math.abs(w[j]) \/ zweit : Infinity;\n    }<\/p>\n<p>    \/\/ Summe zweier Spuren gegen die Leistungssumme: +3 dB gleich und gleichphasig, 0 dB unabhaengig,\n    \/\/ -Unendlich gleich und gegenphasig. b wird um k verschoben (b[n+k] liegt unter a[n]) und mit v gewichtet.\n    function summe(a, b, k, v, anf, ende) {\n        var ea = 0, eb = 0, es = 0, i;\n        for (i = anf; i < ende; i++) {\n            var x = a[i], y = v * b[i + k];\n            ea += x * x; eb += y * y; es += (x + y) * (x + y);\n        }\n        return { gewinn: db10(es \/ (ea + eb)), ea: ea, eb: eb, r: ea > 0 && eb > 0 ? (es - ea - eb) \/ (2 * Math.sqrt(ea * eb)) : null };\n    }<\/p>\n<p>    \/\/ Schlechtestes 100-ms-Fenster, in dem beide Spuren spielen (je hoechstens 20 dB unter ihrem lautesten Fenster)\n    function fensterSumme(a, b, v, anf, ende, fs) {\n        var w = Math.round(FENSTER_S * fs), f = [], i, s, maxa = 0, maxb = 0;\n        for (s = anf; s + w <= ende; s += w) {\n            var ea = 0, eb = 0, es = 0;\n            for (i = s; i < s + w; i++) { var x = a[i], y = v * b[i]; ea += x * x; eb += y * y; es += (x + y) * (x + y); }\n            f.push({ s: s, ea: ea, eb: eb, es: es });\n            if (ea > maxa) { maxa = ea; }\n            if (eb > maxb) { maxb = eb; }\n        }\n        var grenze = Math.pow(10, -AKTIV_DB \/ 10), schlecht = null, n = 0;\n        f.forEach(function (x) {\n            if (x.ea < maxa * grenze || x.eb < maxb * grenze || x.ea === 0 || x.eb === 0) { return; }\n            n++;\n            var g = db10(x.es \/ (x.ea + x.eb));\n            if (schlecht === null || g < schlecht.gewinn) { schlecht = { gewinn: g, zeit_s: x.s \/ fs }; }\n        });\n        return { fenster: n, schlecht: schlecht };\n    }\n\n    function pruefen(kanaeleA, kanaeleB, fs) {\n        var a = mono(kanaeleA), b = mono(kanaeleB);\n        var kk = korrelation(a, b, fs);\n        if (!kk) { return null; }\n        var L = kk.L, anf = kk.anf, ende = kk.ende;\n        var jw = hoechstesBei(kk.w), q = klarheit(kk.w, jw, fs), jr = hoechstesBei(kk.r);\n        \/\/ ein Gipfel am Rand des Suchbereichs liegt vermutlich ausserhalb und zaehlt nicht als klar\n        var klar = q >= Q_KLAR && jw > 1 && jw < 2 * L - 1, j = klar ? jw : jr;\n        var t = fein(klar ? kk.w : kk.r, j), rw = zwischen(kk.r, t);\n        var andere = klar ? [] : nebengipfel(kk.r, jr, L);\n        var e = {\n            fs: fs, verfahren: klar ? 'geweisst' : 'direkt', klarheit: q,\n            versatz_werte: t - L, versatz_ms: 1000 * (t - L) \/ fs, r: rw, r_null: kk.r[L],\n            andere: andere.slice(0, 3).map(function (x) { return { ms: 1000 * x.k \/ fs, r: x.r }; }),\n            analysiert_s: (ende - anf) \/ fs, abstand_cm: Math.abs(t - L) \/ fs * SCHALL_M_S * 100\n        };\n        if (!klar &#038;&#038; Math.abs(kk.r[jr]) < R_MIN) { e.urteil = 'kein_bezug'; }\n        else if (andere.length) { e.urteil = 'mehrdeutig'; }\n        else {\n            var gedreht = rw <= -R_POL;\n            e.urteil = Math.abs(t - L) < 0.5 ? (gedreht ? 'verpolt' : 'deckend') : (gedreht ? 'versatz_verpolt' : 'versatz');\n        }\n        var fest = e.urteil === 'kein_bezug' || e.urteil === 'mehrdeutig';\n        e.schwach = !fest &#038;&#038; Math.abs(rw) < R_SCHWACH;\n        e.polaritaet = fest ? null : (Math.abs(rw) < R_POL ? 'unklar' : (rw < 0 ? 'gedreht' : 'gleich'));\n        var kb = fest ? 0 : Math.round(t - L);\n        if (!fest &#038;&#038; Math.abs(t - L) >= 0.5) { e.kerbe_hz = fs \/ (2 * Math.abs(t - L)); }\n        var sj = summe(a, b, 0, 1, anf, ende);\n        \/\/ nach dem Verschieben beide Polaritaeten, damit auch ein unklarer Fall eine Zahl hat\n        e.summe = { jetzt: sj.gewinn, gedreht: summe(a, b, 0, -1, anf, ende).gewinn, verschoben: fest ? null : summe(a, b, kb, 1, anf, ende).gewinn,\n            verschoben_gedreht: fest ? null : summe(a, b, kb, -1, anf, ende).gewinn };\n        \/\/ Tiefbass: beide Spuren gleich gefiltert, so wie sie jetzt liegen, und mit gedrehter Polaritaet von B\n        var at = tiefpass(a, fs, TIEF_HZ), bt = tiefpass(b, fs, TIEF_HZ);\n        var tj = summe(at, bt, 0, 1, anf, ende), tg = summe(at, bt, 0, -1, anf, ende);\n        e.tief = {\n            jetzt: tj.gewinn, gedreht: tg.gewinn, r: tj.r,\n            anteil_a: db10(tj.ea \/ sj.ea), anteil_b: db10(tj.eb \/ sj.eb),\n            fenster_jetzt: fensterSumme(at, bt, 1, anf, ende, fs), fenster_gedreht: fensterSumme(at, bt, -1, anf, ende, fs)\n        };\n        e.korrektur = fest ? null : { werte: kb, polaritaet: e.polaritaet === 'gedreht' };\n        return e;\n    }<\/p>\n<p>    \/\/ ffmpeg-Befehl fuer Spur B: frueher legen (atrim), spaeter legen (adelay) und\/oder Polaritaet drehen\n    function befehl(e) {\n        if (!e || !e.korrektur) { return ''; }\n        var f = [], k = e.korrektur.werte;\n        if (k > 0) { f.push('atrim=start_sample=' + k, 'asetpts=PTS-STARTPTS'); }\n        if (k < 0) { f.push('adelay=delays=' + (-k) + 'S:all=1'); }\n        if (e.korrektur.polaritaet) { f.push('aeval=-val(ch):c=same'); }\n        return f.length ? 'ffmpeg -i b.wav -af \"' + f.join(',') + '\" b_korrigiert.wav' : '';\n    }\n\n    \/\/ Zwei Dateien oder eine Stereodatei (links gegen rechts)\n    function spurenPruefen(infoA, infoB) {\n        if (!infoB) {\n            if (infoA.kanaele.length < 2) { return { fehler: 'mono' }; }\n            return { modus: 'lr', e: pruefen([infoA.kanaele[0]], [infoA.kanaele[1]], infoA.rate) };\n        }\n        if (infoA.rate !== infoB.rate) { return { fehler: 'rate' }; }\n        return { modus: 'ab', e: pruefen(infoA.kanaele, infoB.kanaele, infoA.rate) };\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 = ' ';\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 || (typeof v === 'number' &#038;&#038; v < -99)) { 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 vorz(v, st) {\n        var s = zahl(v, st);\n        return typeof v === 'number' &#038;&#038; isFinite(v) &#038;&#038; Number(Number(v).toFixed(st === undefined ? 1 : st)) > 0 ? '+' + s : s;\n    }\n    function db(v) { return v === null || v === undefined ? '\u2013' : vorz(v, 1) + NB + 'dB'; }\n    function zeit(s) { var m = Math.floor(s \/ 60), r = s - 60 * m; return m + ':' + (r < 10 ? '0' : '') + zahl(r, 1); }\n    function hz(f) { return f >= 1000 ? zahl(f \/ 1000, f >= 10000 ? 1 : 2) + NB + 'kHz' : zahl(f, 0) + NB + 'Hz'; }<\/p>\n<p>    function kopfUndText(erg) {\n        var e = erg.e, ms = zahl(Math.abs(e.versatz_ms), 3) + NB + 'ms';\n        var werte = zahl(Math.abs(e.korrektur ? e.korrektur.werte : 0), 0);\n        var w = { ms: ms, lage: satz(e.versatz_werte > 0 ? 'spaeter' : 'frueher', { ms: ms }), werte: werte, r: zahl(e.r, 2) };\n        var kopf = satz('urteil_' + e.urteil, w), texte = [satz('urteil_text_' + e.urteil, w)];\n        if (e.urteil === 'mehrdeutig') {\n            texte.push(satz('kandidaten', { liste: [{ ms: e.versatz_ms, r: e.r }].concat(e.andere).map(function (x) {\n                return zahl(x.ms, 2) + NB + 'ms (r' + NB + '=' + NB + zahl(x.r, 2) + ')'; }).join(', ') }));\n        }\n        if (e.schwach) { texte.push(satz('hinweis_schwach', w)); }\n        if (e.polaritaet === 'unklar') { texte.push(satz('hinweis_unklar', w)); }\n        if (e.kerbe_hz) { texte.push(satz('hinweis_kerbe', { hz: hz(e.kerbe_hz), ms: ms, cm: zahl(e.abstand_cm, 1) + NB + 'cm' })); }\n        if (erg.modus === 'lr') { texte.push(T.hinweis_lr); }\n        if (erg.dekodiert) { texte.push(satz('hinweis_dekodiert', { hz: zahl(e.fs, 0) + NB + 'Hz' })); }\n        return { kopf: kopf, texte: texte, stufe: (e.urteil === 'deckend' || e.urteil === 'kein_bezug' || e.urteil === 'mehrdeutig') ? 'gut' : 'tun' };\n    }<\/p>\n<p>    function summenZeilen(e) {\n        var kaum = e.tief.anteil_a < -30 || e.tief.anteil_b < -30;\n        function fenster(f) { return kaum ? T.kaum_tief : (f.schlecht ? db(f.schlecht.gewinn) + ' (' + zeit(f.schlecht.zeit_s) + ')' : '\u2013'); }\n        var z = [\n            [T.fall_jetzt, db(e.summe.jetzt), kaum ? T.kaum_tief : db(e.tief.jetzt), fenster(e.tief.fenster_jetzt)],\n            [T.fall_gedreht, db(e.summe.gedreht), kaum ? T.kaum_tief : db(e.tief.gedreht), fenster(e.tief.fenster_gedreht)]\n        ];\n        if (e.korrektur &#038;&#038; e.korrektur.werte !== 0) {\n            z.push([satz('fall_verschoben', { werte: zahl(e.korrektur.werte, 0) }), db(e.summe.verschoben), '\u2013', '\u2013']);\n            z.push([satz('fall_verschoben_gedreht', { werte: zahl(e.korrektur.werte, 0) }), db(e.summe.verschoben_gedreht), '\u2013', '\u2013']);\n        }\n        return z;\n    }\n\n    function bericht(x) {\n        var e = x.erg.e, ku = kopfUndText(x.erg), z = [T.bericht_kopf + ': ' + x.namen.join(' \/ ')];\n        z.push(ku.kopf);\n        z.push(T.k_versatz + ': ' + zahl(e.versatz_ms, 3) + ' ms (' + zahl(e.versatz_werte, 2) + ' ' + T.werte + ')');\n        z.push(T.k_polaritaet + ': ' + (e.polaritaet ? T['pol_' + e.polaritaet] : '\u2013') + '; r = ' + zahl(e.r, 2));\n        summenZeilen(e).forEach(function (s) { z.push(s.join(' | ')); });\n        var b = befehl(e);\n        if (b) { z.push(b); }\n        return z.join('\\n');\n    }\n\n    function kachel(name, wert) {\n        var d = document.createElement('div'); d.className = 'vp-kachel';\n        var a = document.createElement('div'); a.className = 'vp-kachel-name'; a.textContent = name;\n        var b = document.createElement('div'); b.className = 'vp-kachel-wert'; b.textContent = wert;\n        d.appendChild(a); d.appendChild(b);\n        return d;\n    }\n\n    function darstellen(x) {\n        var e = x.erg.e;\n        if (!el('vp-kacheln')) { return x; }\n        var fest = e.urteil === 'kein_bezug' || e.urteil === 'mehrdeutig', k = el('vp-kacheln');\n        k.textContent = '';\n        k.appendChild(kachel(T.k_versatz, fest ? '\u2013' : vorz(e.versatz_ms, 3) + NB + 'ms'));\n        k.appendChild(kachel(T.k_werte, fest ? '\u2013' : vorz(e.versatz_werte, 2)));\n        k.appendChild(kachel(T.k_polaritaet, e.polaritaet ? T['pol_' + e.polaritaet] : '\u2013'));\n        k.appendChild(kachel(T.k_r, zahl(e.r, 2)));\n        var ku = kopfUndText(x.erg), u = el('vp-urteil');\n        u.className = 'vp-urteil vp-u-' + ku.stufe;\n        u.textContent = '';\n        var kopf = document.createElement('div'); kopf.className = 'vp-urteil-kopf'; kopf.textContent = ku.kopf;\n        u.appendChild(kopf);\n        ku.texte.forEach(function (t) { var p = document.createElement('p'); p.className = 'vp-klein'; p.textContent = t; u.appendChild(p); });\n        var b = befehl(e);\n        if (b) {\n            var bb = document.createElement('div'); bb.className = 'vp-befehl'; bb.id = 'vp-befehl'; bb.textContent = b;\n            var bh = document.createElement('p'); bh.className = 'vp-klein'; bh.textContent = T.befehl_hinweis;\n            u.appendChild(bb); u.appendChild(bh);\n        }\n        var t = el('vp-summen'), namen = [T.sp_fall, T.sp_gesamt, T.sp_tief, T.sp_fenster];\n        t.textContent = '';\n        summenZeilen(e).forEach(function (zeile) {\n            var tr = document.createElement('tr');\n            zeile.forEach(function (w, i) {\n                var td = document.createElement('td');\n                if (i > 0) { td.className = 'vp-wert'; td.setAttribute('data-name', namen[i]); }\n                td.textContent = w; tr.appendChild(td);\n            });\n            t.appendChild(tr);\n        });\n        el('vp-summe-hinweis').textContent = satz('summe_hinweis', { s: zahl(e.analysiert_s, 1) + NB + 's' });\n        el('vp-ausgabe').hidden = false;\n        el('vp-fehler').hidden = true;\n        return x;\n    }<\/p>\n<p>    function fehlerZeigen(s) {\n        var f = el('vp-fehler');\n        if (!f) { return; }\n        f.textContent = s; f.hidden = false;\n        el('vp-ausgabe').hidden = true;\n        el('vp-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>    \/\/ Eine Datei vorab: WAV und AIFF direkt gelesen, alles andere mit Format und Rate aus dem Kopf fuer den Decoder\n    function vorlesen(puffer) {\n        var info = null;\n        try { info = direktLesen(puffer); } catch (x) { info = null; }\n        if (info) { return { info: info }; }\n        var kopf = kopfRate(puffer);\n        return kopf.format ? { kopf: kopf, puffer: puffer } : { fehler: 'format' };\n    }\n    \/\/ Der Decoder des Browsers rechnet auf die Rate seines Kontexts um. Deshalb wird eine dekodierte Spur gleich\n    \/\/ in der Rate der anderen Spur dekodiert; ohne direkt gelesene Spur in der Rate aus dem Kopf von A.\n    function fertiglesen(v, ziel) {\n        if (v.info) { return Promise.resolve(v.info); }\n        return dekodieren(v.puffer.slice(0), ziel).then(function (b) {\n            var k = [];\n            for (var c = 0; c < b.numberOfChannels; c++) { k.push(b.getChannelData(c)); }\n            return { format: v.kopf.format, rate: b.sampleRate, kanaele: k, dekodiert: true };\n        }, function () { return Promise.reject('lesen'); });\n    }\n    function passend(r) { return r &#038;&#038; r >= 3000 && r <= 768000 ? r : null; }\n\n    var letztes = null;\n    \/\/ pufferA, pufferB (oder null fuer links gegen rechts), Namen fuer den Bericht\n    function verarbeiten(pufferA, pufferB, namen) {\n        if (el('vp-status')) { el('vp-status').textContent = T.status_lesen; }\n        var va = vorlesen(pufferA), vb = pufferB ? vorlesen(pufferB) : null;\n        if (va.fehler || (vb &#038;&#038; vb.fehler)) { fehlerZeigen(T.fehler_format); return Promise.resolve(null); }\n        var ziel = va.info ? va.info.rate : (vb &#038;&#038; vb.info ? vb.info.rate : (passend(va.kopf.rate) || (vb &#038;&#038; passend(vb.kopf.rate)) || 48000));\n        return Promise.all([fertiglesen(va, ziel), vb ? fertiglesen(vb, ziel) : Promise.resolve(null)]).then(function (i) {\n            var a = i[0], b = i[1], n = Math.min(a.kanaele[0].length, b ? b.kanaele[0].length : Infinity);\n            if (b &#038;&#038; a.rate !== b.rate) { fehlerZeigen(satz('fehler_rate', { a: a.rate, b: b.rate })); return null; }\n            if (!b &#038;&#038; a.kanaele.length < 2) { fehlerZeigen(T.fehler_mono); return null; }\n            if (n < a.rate * 0.25) { fehlerZeigen(T.fehler_kurz); return null; }\n            if (el('vp-status')) { el('vp-status').textContent = T.status_messen; }\n            return new Promise(function (ok) {\n                spaeter(function () {\n                    var erg = spurenPruefen(a, b);\n                    if (el('vp-status')) { el('vp-status').textContent = ''; }\n                    if (!erg || erg.fehler || !erg.e) { fehlerZeigen(T.fehler_kurz); ok(null); return; }\n                    erg.dekodiert = [a, b].some(function (x) { return x &#038;&#038; x.dekodiert; });\n                    letztes = { erg: erg, namen: namen || [] };\n                    ok(darstellen(letztes));\n                });\n            });\n        }, function (grund) { fehlerZeigen(grund === 'format' ? T.fehler_format : T.fehler_lesen); return null; });\n    }\n\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.VP = { T: T, spurenPruefen: spurenPruefen, direktLesen: direktLesen, befehl: befehl, verarbeiten: verarbeiten,\n        bericht: bericht, summenZeilen: summenZeilen, letztes: function () { return letztes; } };\n\n    var gewaehlt = { a: null, b: null }, messen = el('vp-messen');\n    if (!messen) { return; }\n    function nehmen(seite, datei) {\n        if (!datei) { return; }\n        var leser = new FileReader();\n        leser.onload = function () {\n            gewaehlt[seite] = { puffer: leser.result, name: datei.name };\n            el('vp-name-' + seite).textContent = datei.name;\n            messen.disabled = !gewaehlt.a;\n        };\n        leser.onerror = function () { fehlerZeigen(T.fehler_lesen); };\n        try { leser.readAsArrayBuffer(datei); } catch (x) { fehlerZeigen(T.fehler_lesen); }\n    }\n    ['a', 'b'].forEach(function (seite) {\n        var zone = el('vp-zone-' + seite), eingabe = el('vp-datei-' + seite);\n        el('vp-knopf-' + seite).addEventListener('click', function () { eingabe.click(); });\n        eingabe.addEventListener('change', function () { nehmen(seite, 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('vp-ueber'); }); });\n        ['dragleave', 'drop'].forEach(function (t) { zone.addEventListener(t, function (ev) { ev.preventDefault(); zone.classList.remove('vp-ueber'); }); });\n        zone.addEventListener('drop', function (ev) { ev.preventDefault(); nehmen(seite, ev.dataTransfer &#038;&#038; ev.dataTransfer.files &#038;&#038; ev.dataTransfer.files[0]); });\n    });\n    messen.addEventListener('click', function () {\n        if (!gewaehlt.a) { return; }\n        verarbeiten(gewaehlt.a.puffer, gewaehlt.b ? gewaehlt.b.puffer : null, [gewaehlt.a.name].concat(gewaehlt.b ? [gewaehlt.b.name] : []));\n    });\n    el('vp-leeren').addEventListener('click', function () {\n        gewaehlt = { a: null, b: null };\n        el('vp-name-a').textContent = ''; el('vp-name-b').textContent = '';\n        messen.disabled = true;\n        el('vp-ausgabe').hidden = true; el('vp-fehler').hidden = true; el('vp-status').textContent = '';\n    });\n    var kop = el('vp-kopieren'), kopiert = el('vp-kopiert');\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<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Measures the offset between two tracks, or between left and right, to a fraction of a sample, detects reversed polarity and computes what the sum gains or loses, overall and below 150\u00a0Hz. Returns the ffmpeg command that shifts and inverts the second track.<\/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-20240","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>Offset and Polarity Checker: time offset and reversed polarity of two tracks - 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\/time-offset-polarity-checker-two-tracks\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Offset and Polarity Checker: time offset and reversed polarity of two tracks - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Measures the offset between two tracks, or between left and right, to a fraction of a sample, detects reversed polarity and computes what the sum gains or loses, overall and below 150\u00a0Hz. Returns the ffmpeg command that shifts and inverts the second track.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-23T19:59:40+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=\"4 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\\\/time-offset-polarity-checker-two-tracks\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/time-offset-polarity-checker-two-tracks\\\/\",\"name\":\"Offset and Polarity Checker: time offset and reversed polarity of two tracks - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-23T19:37:48+00:00\",\"dateModified\":\"2026-09-23T19:59:40+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/time-offset-polarity-checker-two-tracks\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/time-offset-polarity-checker-two-tracks\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/time-offset-polarity-checker-two-tracks\\\/#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\":\"Offset and Polarity Checker: time offset and reversed polarity of two tracks\"}]},{\"@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":"Offset and Polarity Checker: time offset and reversed polarity of two tracks - 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\/time-offset-polarity-checker-two-tracks\/","og_locale":"en_US","og_type":"article","og_title":"Offset and Polarity Checker: time offset and reversed polarity of two tracks - Lukas Wojcik - Blog","og_description":"Measures the offset between two tracks, or between left and right, to a fraction of a sample, detects reversed polarity and computes what the sum gains or loses, overall and below 150\u00a0Hz. Returns the ffmpeg command that shifts and inverts the second track.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-09-23T19:59:40+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":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/","name":"Offset and Polarity Checker: time offset and reversed polarity of two tracks - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-23T19:37:48+00:00","dateModified":"2026-09-23T19:59:40+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/time-offset-polarity-checker-two-tracks\/#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":"Offset and Polarity Checker: time offset and reversed polarity of two tracks"}]},{"@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\/20240","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=20240"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/20240\/revisions"}],"predecessor-version":[{"id":20307,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/20240\/revisions\/20307"}],"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=20240"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=20240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}