blob: 108373919a079be947d632cb467c07f0fd48507c [file] [edit]
<!DOCTYPE>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<style>
body, html {
font-family: monospace;
font-size: 16px;
}
button {
font-size: 16px;
}
.container {
padding: 1em;
margin: 4px;
border: 1px solid black;
}
.button-container {
margin-bottom: 1em;
}
.audio-fp-result {
font-size: 20px;
display: block;
margin-bottom: 6px;
}
</style>
</head>
<script>
function combineSamples(samples) {
let result = 0;
for (const value of samples) {
if (isFinite(value))
result += value;
}
return result;
}
function waitForAudioProcess(scriptProcessor) {
return new Promise(resolve => {
scriptProcessor.addEventListener("audioprocess", event => {
resolve(event);
}, { once: true });
});
}
async function testOscillatorCompressor() {
const context = new OfflineAudioContext(1, 5000, 44100);
const oscillator = context.createOscillator();
oscillator.type = "triangle";
oscillator.frequency.value = 1000;
const compressor = context.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.knee.value = 40;
compressor.ratio.value = 12;
compressor.reduction.value = 20;
compressor.attack.value = 0;
compressor.release.value = 0.2;
oscillator.connect(compressor);
compressor.connect(context.destination);
oscillator.start();
const renderedBuffer = await context.startRendering();
oscillator.disconnect();
compressor.disconnect();
return combineSamples(renderedBuffer.getChannelData(0));
}
function testOscillatorCompressorWorklet() {
return new Promise(async resolve => {
const context = new AudioContext;
await context.audioWorklet.addModule("fingerprint-audio-worklet.js");
const workletNode = new AudioWorkletNode(context, "fingerprint-processor");
let gotFingerprint = false;
workletNode.port.onmessage = event => {
const fingerprint = event?.data?.fingerprint || 0;
if (!fingerprint || gotFingerprint)
return;
gotFingerprint = true;
oscillator.disconnect();
compressor.disconnect();
workletNode.disconnect();
resolve(fingerprint);
};
const oscillator = context.createOscillator();
oscillator.type = "triangle";
oscillator.frequency.value = 1000;
const compressor = context.createDynamicsCompressor();
compressor.threshold.value = -100;
compressor.knee.value = 50;
compressor.ratio.value = 8;
compressor.reduction.value = 10;
compressor.attack.value = 0.1;
compressor.release.value = 0.12;
oscillator.connect(compressor).connect(workletNode).connect(context.destination);
oscillator.start();
context.resume();
});
}
async function testOscillatorCompressorAnalyzer() {
const context = new AudioContext;
const oscillator = context.createOscillator();
const analyzer = context.createAnalyser();
const gain = context.createGain();
const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
const compressor = context.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.knee.value = 40;
compressor.ratio.value = 12;
compressor.reduction.value = 20;
compressor.attack.value = 0;
compressor.release.value = 0.2;
gain.gain.value = 0;
oscillator.type = "triangle";
oscillator.frequency.value = 1000;
oscillator.connect(compressor);
compressor.connect(analyzer);
analyzer.connect(scriptProcessor);
scriptProcessor.connect(gain);
gain.connect(context.destination);
oscillator.start(0);
await waitForAudioProcess(scriptProcessor);
const bins = new Float32Array(analyzer.frequencyBinCount);
analyzer.getFloatFrequencyData(bins);
oscillator.disconnect();
compressor.disconnect();
analyzer.disconnect();
scriptProcessor.disconnect();
gain.disconnect();
return combineSamples(bins);
}
async function testLoopingOscillatorCompressorBiquadFilter() {
const sampleRate = 44100;
const cloneCount = 40000;
const targetSampleIndex = 3395;
const stabilizationPrecision = 6.2;
async function getBaseAudioFingerprint() {
const baseSignal = await (async function() {
const context = new OfflineAudioContext(1, targetSampleIndex + 1, 44100);
const oscillator = context.createOscillator();
oscillator.type = "square";
oscillator.frequency.value = 1000;
const compressor = context.createDynamicsCompressor();
compressor.threshold.value = -70;
compressor.knee.value = 40;
compressor.ratio.value = 12;
compressor.attack.value = 0;
compressor.release.value = 0.25;
const filter = context.createBiquadFilter();
filter.type = "allpass";
filter.frequency.value = 5.239622852977861;
filter.Q.value = 0.1;
oscillator.connect(compressor);
compressor.connect(filter);
filter.connect(context.destination);
oscillator.start(0);
return await context.startRendering();
})();
const context = new OfflineAudioContext(1, baseSignal.length - 1 + cloneCount, sampleRate);
const sourceNode = context.createBufferSource();
sourceNode.buffer = baseSignal;
sourceNode.loop = true;
sourceNode.loopStart = (baseSignal.length - 1) / sampleRate;
sourceNode.loopEnd = baseSignal.length / sampleRate;
sourceNode.connect(context.destination);
sourceNode.start();
const clonedSignal = await context.startRendering();
const fingerprint = extractFingerprint(baseSignal, clonedSignal.getChannelData(0).subarray(baseSignal.length - 1));
return Math.abs(fingerprint);
}
function extractFingerprint(baseSignal, clonedSample) {
let fingerprint;
let needsDenoising = false;
for (let i = 0; i < clonedSample.length; i += Math.floor(clonedSample.length / 10)) {
if (clonedSample[i] === 0) {
// Intentionally ignored.
continue;
}
if (fingerprint === undefined) {
fingerprint = clonedSample[i];
continue;
}
if (fingerprint !== clonedSample[i]) {
needsDenoising = true;
break;
}
}
if (fingerprint === undefined)
fingerprint = baseSignal.getChannelData(0)[baseSignal.length - 1];
else if (needsDenoising)
fingerprint = getMean(clonedSample);
return fingerprint;
}
function getMean(signal) {
let total = 0;
let finiteValueCount = 0;
for (let i = 0; i < signal.length; i++) {
const value = signal[i];
if (!isFinite(value))
continue;
total += value;
finiteValueCount++;
}
return finiteValueCount ? (total / finiteValueCount) : 0;
}
function stabilize(value, precision) {
if (value === 0)
return value;
const power = Math.floor(Math.log10(Math.abs(value)));
const precisionPower = power - Math.floor(precision) + 1;
const precisionBase = 10 ** -precisionPower * ((precision * 10) % 10 || 1);
return Math.round(value * precisionBase) / precisionBase;
}
const rawFingerprint = await getBaseAudioFingerprint();
return stabilize(rawFingerprint, stabilizationPrecision);
}
</script>
</body>
</html>