{"version":3,"sources":["jsdelivr-header.js","/npm/audio-compare@1.0.1/index.js"],"names":[],"mappings":"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","file":"/npm/audio-compare@1.0.1/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/audio-compare@1.0.1/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","// Port of https://github.com/kdave/audio-compare to nodejs module\n\nimport { accessSync, constants, readFileSync } from 'node:fs';\nimport numpy from 'jsnumpy';\nimport cli from 'simple-cli-parser';\nimport bigInt from 'big-integer';\nimport bin from 'dec-to-binary';\n\nconst defaultOptions = {\n    // seconds to sample audio file for\n    sample_time: 500,\n    // number of points to scan cross correlation over\n    span: 100,\n    // step size(in points) of cross correlation\n    step: 1,\n    // minimum number of points that must overlap in cross correlation\n    // exception is raised if this cannot be met\n    min_overlap: 20,\n    // report match when cross correlation has a peak exceeding threshold\n    threshold: 0.5,\n}\n\nlet options;\n\nfunction fileExists(path) {\n    let found = false;\n    try {\n        accessSync(path, constants.R_OK);\n        found = true;\n    } catch (err) {\n        // file read failure\n    }\n\n    return found;\n}\n\nfunction validate(config) {\n    if (!config) {\n        throw new Error('You need to supply a config object');\n    }\n\n    if (!config.source || !config.dest) {\n        throw new Error('You need to supply config.source and config.dest');\n    } \n\n    if (!fileExists(config.source)){\n        throw new Error('The file supplied for config.source does not exist or you do not have access');\n    }\n\n    if (!fileExists(config.dest)) {\n        throw new Error('The file supplied for config.dest does not exist or you do not have access');\n    }\n}\n\n// calculate fingerprint\n// Generate file.mp3.fpcalc by \"fpcalc -raw -length 500 file.mp3\"\nasync function calculate_fingerprints(filename) {\n    let fpcalc_out;\n\n    if (fileExists(filename + '.fpcalc')){\n        console.log(`Found precalculated fingerprint for ${filename}`);\n        fpcalc_out = readFileSync(filename, 'utf-8').split(/\\r?\\n/).join('');\n    } else {\n        console.log(`Calculating fingerprint by fpcalc for ${filename}`);\n        const content = await (new cli(['fpcalc', '-raw', '-length', options.sample_time.toString(), filename]));\n        fpcalc_out = content.trim().replace('\\\\n', '').replace('\\'', '');\n    }\n\n    const fingerprint_index = fpcalc_out.indexOf('FINGERPRINT=') + 12;\n\n    // convert fingerprint to list of integers\n    const fingerprints = fpcalc_out.substring(fingerprint_index).split(',').map((f) => parseInt(f, 10));\n\n    return fingerprints;\n}\n\nfunction correlation(listx, listy) {\n    if (!(listx?.length > 0 && listy?.length > 0)) {\n        //Error checking in main program should prevent us from ever being able to get here.\n        throw Error('Empty lists cannot be correlated.');\n    }\n\n    if (listx.length > listy.length) {\n        listx = listx.slice(0, listy.length);\n    } else if (listx.length < listy.length) {\n        listy = listy.slice(0, listx.length);\n    }\n\n    let covariance = 0;\n\n    for (let i = 0; i < listx.length; i++){\n        let xor = parseFloat(bigInt(listx[i]).xor(listy[i]).value, 10);\n        covariance += 32 - (bin.decimal(xor).split('1').length - 1);\n    }\n\n    covariance = covariance / parseFloat(listx.length, 10);\n\n    return covariance / 32;\n}\n\n// return cross correlation, with listy offset from listx\nfunction cross_correlation(listx, listy, offset) {\n    if (offset > 0) {\n        listx = listx.slice(offset);\n        listy = listy.slice(0, listx.length);\n    } else if (offset < 0) {\n        offset = -offset;\n        listy = listy.slice(offset);\n        listx = listx.slice(0, listy.length);\n    }\n\n    if (Math.min(listx.length, listy.length) < options.min_overlap) {\n        // Error checking in main program should prevent us from ever being able to get here.\n        return;\n        // throw Error('Overlap too small: %i' % min(len(listx), len(listy)))\n    }\n    \n    return correlation(listx, listy);\n}\n\n// cross correlate listx and listy with offsets from - span to span\nfunction compareCorrelate(listx, listy, span, step) {\n    const min = Math.min(listx.length, listy.length);\n    if (options.span > min) {\n        throw Error(`span >= sample size: ${(options.span)} >= ${min}\\nReduce span, reduce crop or increase sample_time.`);\n    }\n\n    const corr_xy = [];\n\n    for (let offset of numpy.arange(-span, span, step)) {\n        corr_xy.push(cross_correlation(listx, listy, offset));\n    }\n\n    return corr_xy;\n}\n\n// return index of maximum value in list\nfunction max_index(listx) {\n    let max_index = 0;\n    let max_value = listx[0];\n\n    listx.map((value, i) => {\n        if (value > max_value) { max_value = value; max_index = i; }\n    });\n    \n    return max_index;\n}\n\nfunction get_max_corr(corr, source, target) {\n    const max_corr_index = max_index(corr);\n    const max_corr_offset = -options.span + max_corr_index * options.step;\n\n    return {\n        max_corr_index,\n        max_corr_offset,\n        source,\n        target,\n        match: corr[max_corr_index] * 100.0,\n    };\n}\n\nasync function correlate(source, target) {\n    const fingerprint_source = await calculate_fingerprints(source);\n    const fingerprint_target = await calculate_fingerprints(target);\n\n    const corr = compareCorrelate(fingerprint_source, fingerprint_target, options.span, options.step);\n    return get_max_corr(corr, source, target);\n}\n\n\nexport async function compare(config) {\n    validate(config);\n    options = Object.assign({}, defaultOptions, config.options);\n\n    return await correlate(config.source, config.dest)\n}\n\nexport default {};"]}