UNPKG

maia-hash

Version:

Hashing and fingerprinting functions supporting various applications by Music Artificial Intelligence Algorithms, Inc.

1,348 lines (1,265 loc) 53.3 kB
var mf = (function () { 'use strict'; // Imports const fs$1 = require("fs"); const path$1 = require("path"); require("maia-util"); // import PointSet from './PointSet' class OntimePitchHasher$1 { constructor(_mapPath) { if (_mapPath !== undefined) { this.map = require(_mapPath); } else { this.map = {}; } } contains(aKey) { return this.map[aKey] } // The expected format is with ontime in the first dimension and pitch in the // second dimension of pts. It is assumed that pts is sorted // lexicographically. create_hash_entries( pts, cumuTime, fnam, mode = "duples", insertMode = "increment and file with fnams", tMin = 0.1, tMax = 10, pMin = 1, pMax = 12, folder = __dirname ) { const npts = pts.length; // console.log("npts:", npts) let nh = 0; switch (mode) { case "duples": for (let i = 0; i < npts - 1; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts) { const v1 = pts[j]; const td = v1[0] - v0[0]; const apd = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to make a hash entry. if (td > tMin && td < tMax && apd >= pMin && apd <= pMax) { // Make a hash entry, something like "±pdtd" const he = this.create_hash_entry( [v1[1] - v0[1], td], mode, cumuTime + v0[0], fnam, tMin, tMax ); this.insert(he); nh++; } // End whether to make a hash entry. if (td >= tMax) { j = npts - 1; } j++; } // End while. } // for (let i = 0; break case "triples": for (let i = 0; i < npts - 2; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts - 1) { const v1 = pts[j]; const td1 = v1[0] - v0[0]; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = pts[k]; const td2 = v2[0] - v1[0]; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { // Make a hash entry, something like "±pd1±pd2tdr" const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, cumuTime + v0[0], fnam, tMin, tMax ); this.insert(he, insertMode, folder); nh++; } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { j = npts - 2; } j++; } // End j while. } // for (let i = 0; break default: console.log("Should not get to default in create_hash_entries() switch."); } return nh } create_hash_entry(vals, mode, ctime, fnam, tMin, tMax) { let str = "", isInteger; switch (mode) { case "duples": // Sense-check pitch difference. const apd = Math.abs(vals[0]); if (apd >= 100 || Math.round(vals[0]) !== vals[0]) { console.log("Unexpected pitch difference:", vals[0]); console.log("Returning."); return } if (vals[0] >= 0) { str += "+"; } else { str += "-"; } if (apd < 10) { str += "0"; } str += apd; // Sense-check time difference. isInteger = Math.round(vals[1]) === vals[1]; if (vals[1] >= tMax || vals[1] < tMin) { console.log("Unexpected time difference:", vals[1]); console.log("Returning."); return } // Round time difference to 1 d.p. and append to str. str += Math.round(10 * vals[1]) / 10; if (isInteger) { str += ".0"; } break case "triples": // Sense-check pitch difference. vals.slice(0, 2).forEach(function (v, idx) { const apd = Math.abs(v); if (apd >= 100 || Math.round(v) !== v) { console.log("Unexpected pitch difference:", v, idx); console.log("Returning."); return } if (v >= 0) { str += "+"; } else { str += "-"; } if (apd < 10) { str += "0"; } str += apd; }); // Sense-check time difference ratio. if (vals[2] >= tMax / tMin || vals[2] < tMin / tMax) { console.log("Unexpected time difference:", vals[2]); console.log("Returning."); return } // If ratio less than 1, invert and give it a negative sign so that such // values are as accurately represented as positive values. // console.log("vals[2] before inversion:", vals[2]) let sign = "+"; if (vals[2] < 1) { vals[2] = 1 / vals[2]; sign = "-"; } // console.log("vals[2] after inversion:", vals[2]) str += sign; // Round time difference ratio to 1 d.p. and append to str. const dp1 = Math.round(10 * vals[2]) / 10; isInteger = Math.round(dp1) === dp1; // console.log("isInteger:", isInteger) str += dp1; if (isInteger) { str += ".0"; } // console.log("str:", str) break default: console.log("Should not get to default in create_hash_entry() switch."); } return { "hash": str, "ctimes": [ctime], "fnams": [fnam] } } // This method is inefficient and could be improved. I don't think it's worth // obtaining the name of each piece when most of the entries of countBins // are zero. Therefore, I've sliced it to topN. get_piece_names(countBins, ctimes, fnams, binSize, topN = 100){ let out = []; // "out" contains the index of bin, // and it is sorted based on the corresponding number of hash entries contained in "hist". for (let i = 0; i < countBins.length; i++) { out.push(i); } out.sort(function (a, b) { return countBins[b] - countBins[a] }); out = out.slice(0, topN); return out.map((idx) => { for (let i = 0; i < ctimes.length; i++){ if (idx*binSize <= ctimes[i]){ return { "winningPiece": fnams[i - 1], "edge": idx * binSize, "count": countBins[idx] } } } }) } insert(hashEntry, method = "hash and lookup", dir) { const key = hashEntry.hash; const lookup = this.contains(key); switch (method) { case "hash and lookup": if (lookup !== undefined) { // Extend ctimes and fnams arrays. lookup.ctimes.push(hashEntry.ctimes[0]); lookup.fnams.push(hashEntry.fnams[0]); } else { delete hashEntry.hash; this.map[key] = hashEntry; } break case "increment and file with fnams": if (lookup !== undefined) { this.map[key].increment++; } else { this.map[key] = { "increment": 1, "log": fs$1.openSync(path$1.join(dir, key + ".json"), "a") }; } fs$1.writeSync( this.map[key].log, JSON.stringify( [ Math.round(100 * hashEntry.ctimes[0]) / 100, hashEntry.fnams[0] ] ) + "," ); break case "increment and file": if (lookup !== undefined) { this.map[key].increment++; } else { this.map[key] = { "increment": 1, "log": fs$1.openSync( path$1.join(dir, key + ".json"), "a" // {"flags": "a"} ) }; } const content = JSON.stringify(Math.round(100 * hashEntry.ctimes[0]) / 100) + ","; // 82.3MB // const content = JSON.stringify(Math.round(10 * hashEntry.ctimes[0]) / 10) + "," // 72.MB // const content = JSON.stringify(hashEntry.ctimes[0]) + "," // 162.9MB fs$1.writeSync(this.map[key].log, content); // this.map[key].log.write(content) // fs.writeFileSync( // path.join(dir, key + ".json"), // JSON.stringify( // [ // Math.round(10*hashEntry.ctimes[0])/10, // hashEntry.fnams[0] // ] // ) // + ",", // { "flag": "a" } // ) break default: console.log("Should not get to default in insert()!"); } } // The expected format is with time in the first dimension and pitch in the // second dimension of pts. It is assumed that pts is sorted // lexicographically. match_hash_entries( pts, mode = "duples", tMin, tMax, pMin, pMax, ctimes, binSize, folder = __dirname ) { let uninh = new Set(); const bins = Math.ceil(ctimes[ctimes.length - 1] / binSize); let countBins = new Array(bins).fill(0).map(() => { return new Set() }); pts = pts.slice(0, 80); const npts = pts.length; let nh = 0; switch (mode) { case "duples": for (let i = 0; i < npts - 1; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts) { const v1 = pts[j]; const td = v1[0] - v0[0]; const apd = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to make a hash entry. if (td > tMin && td < tMax && apd >= pMin && apd <= pMax) { // Make a hash entry, something like "±pdtd" const he = this.create_hash_entry( [v1[1] - v0[1], td], mode, v0[0] ); // console.log("he:", he) // Is there a match? const lookup = this.contains(he.hash); if (lookup !== undefined) { // There's a match! lookup.ctimes.forEach(function (ctime) { tInDset.push(ctime); tInQuery.push(he.ctimes[0]); }); } nh++; } // End whether to make a hash entry. if (td >= tMax) { j = npts - 1; } j++; } // End while. } // for (let i = 0; break case "triples": loop1: for (let i = 0; i < npts - 2; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts - 1) { const v1 = pts[j]; const td1 = v1[0] - v0[0]; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = pts[k]; const td2 = v2[0] - v1[0]; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, v0[0] ); if (fs$1.existsSync(path$1.join(folder, he.hash + ".json"))) { const lookupStr = fs$1.readFileSync( path$1.join(folder, he.hash + ".json"), "utf8" ).slice(0, -1); let lookup = JSON.parse("[" + lookupStr + "]"); lookup.forEach((value) => { let dif = value - he.ctimes[0]; if (dif >= 0 && dif <= ctimes[ctimes.length - 1]) { countBins[Math.floor(dif / binSize)].add(he.hash); } }); } uninh.add(he.hash); nh++; if (nh > 5000) { break loop1 } } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { j = npts - 2; } j++; } // End j while. } // for (let i = 0; break default: console.log("Should not get to default in match_hash_entries() switch."); } return { "nosHashes": nh, "uninosHashes": uninh.size, "countBins": countBins.map((value => { return value.size })) } } } // Imports const fs = require("fs"); const path = require("path"); require("maia-util"); // import PointSet from './PointSet' class HasherNoConcat$1 { constructor(_mapPath) { if (_mapPath !== undefined) { this.map = require(_mapPath); } else { this.map = {}; } } contains(aKey) { return this.map[aKey] } // The expected format is with ontime in the first dimension and pitch in the // second dimension of pts. It is assumed that pts is sorted // lexicographically. create_hash_entries( pts, fnam, mode = "duples", insertMode = "increment and file with fnams", tMin = 0.1, tMax = 10, pMin = 1, pMax = 12, folder = __dirname ) { const npts = pts.length; // console.log("npts:", npts) let nh = 0; switch (mode) { case "duples": for (let i = 0; i < npts - 1; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts) { const v1 = pts[j]; const td = v1[0] - v0[0]; const apd = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to make a hash entry. if (td > tMin && td < tMax && apd >= pMin && apd <= pMax) { // Make a hash entry, something like "±pdtd" const he = this.create_hash_entry( [v1[1] - v0[1], td], mode, v0[0], fnam, tMin, tMax ); this.insert(he); nh++; } // End whether to make a hash entry. if (td >= tMax) { j = npts - 1; } j++; } // End while. } // for (let i = 0; break case "triples": for (let i = 0; i < npts - 2; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts - 1) { const v1 = pts[j]; const td1 = Math.round(100000*(v1[0] - v0[0]))/100000; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = pts[k]; const td2 = Math.round(100000*(v2[0] - v1[0]))/100000; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { // Make a hash entry, something like "±pd1±pd2tdr" const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, v0[0], fnam, tMin, tMax ); this.insert(he, insertMode, folder, [i, j, k]); nh++; } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { j = npts - 2; } j++; } // End j while. } // for (let i = 0; break default: console.log("Should not get to default in create_hash_entries() switch."); } return nh } create_hash_entry(vals, mode, ctime, fnam, tMin, tMax) { let str = "", isInteger; switch (mode) { case "duples": // Sense-check pitch difference. const apd = Math.abs(vals[0]); if (apd >= 100 || Math.round(vals[0]) !== vals[0]) { console.log("Unexpected pitch difference:", vals[0]); console.log("Returning."); return } if (vals[0] >= 0) { str += "+"; } else { str += "-"; } if (apd < 10) { str += "0"; } str += apd; // Sense-check time difference. isInteger = Math.round(vals[1]) === vals[1]; if (vals[1] >= tMax || vals[1] < tMin) { console.log("Unexpected time difference:", vals[1]); console.log("Returning."); return } // Round time difference to 1 d.p. and append to str. str += Math.round(10 * vals[1]) / 10; if (isInteger) { str += ".0"; } break case "triples": // Sense-check pitch difference. vals.slice(0, 2).forEach(function (v, idx) { const apd = Math.abs(v); if (apd >= 100 || Math.round(v) !== v) { console.log("Unexpected pitch difference:", v, idx); console.log("Returning."); return } if (v >= 0) { str += "+"; } else { str += "-"; } if (apd < 10) { str += "0"; } str += apd; }); // Sense-check time difference ratio. if (vals[2] >= tMax / tMin || vals[2] < tMin / tMax) { console.log("Unexpected time difference:", vals[2]); console.log("Returning."); return } // If ratio less than 1, invert and give it a negative sign so that such // values are as accurately represented as positive values. // console.log("vals[2] before inversion:", vals[2]) let sign = "+"; if (vals[2] < 1) { vals[2] = 1 / vals[2]; sign = "-"; } // console.log("vals[2] after inversion:", vals[2]) str += sign; // Round time difference ratio to 1 d.p. and append to str. const dp1 = Math.round(10 * vals[2]) / 10; isInteger = Math.round(dp1) === dp1; // console.log("isInteger:", isInteger) str += dp1; if (isInteger) { str += ".0"; } // console.log("str:", str) break default: console.log("Should not get to default in create_hash_entry() switch."); } return { "hash": str, "ctimes": [ctime], "fnams": [fnam] } } // Obsolete get_piece_names(countBins, binSize, topN = 100){ let out = []; countBins.forEach(function(value, key){ const pieceHist = countBins.get(key); pieceHist.forEach(function(setSize, idx){ out.push( { "pieceId": key, "index": idx, "setSize": setSize } ); }); }); // "out" contains the index of bin, // and it is sorted based on the corresponding number of hash entries contained in "hist". out.sort(function (a, b) { return b.setSize - a.setSize }); out = out.slice(0, topN); return out.map(function(entry){ return { "winningPiece": entry.pieceId, "edge": entry.index * binSize, "count": entry.setSize } }) } insert(hashEntry, method = "hash and lookup", dir, tripleIdx) { const key = hashEntry.hash; const lookup = this.contains(key); switch (method) { case "hash and lookup": if (lookup !== undefined) { // Extend ctimes and fnams arrays. lookup.ctimes.push(hashEntry.ctimes[0]); lookup.fnams.push(hashEntry.fnams[0]); } else { delete hashEntry.hash; this.map[key] = hashEntry; } break case "increment and file with fnams": if (lookup !== undefined) { this.map[key].increment++; } else { this.map[key] = { "increment": 1, "log": fs.openSync(path.join(dir, key + ".json"), "a") }; } fs.writeSync( this.map[key].log, JSON.stringify( [ Math.round(100 * hashEntry.ctimes[0]) / 100, hashEntry.fnams[0] ] ) + "," ); break case "increment and file with fnams and tripleIdx": // save triples of points for visualising if (lookup !== undefined) { this.map[key].increment++; } else { this.map[key] = { "increment": 1, "log": fs.openSync(path.join(dir, key + ".json"), "a") }; } fs.writeSync( this.map[key].log, JSON.stringify( [ Math.round(100 * hashEntry.ctimes[0]) / 100, hashEntry.fnams[0], tripleIdx ] ) + "," ); break case "increment and file": if (lookup !== undefined) { this.map[key].increment++; } else { this.map[key] = { "increment": 1, "log": fs.openSync( path.join(dir, key + ".json"), "a" // {"flags": "a"} ) }; } const content = JSON.stringify(Math.round(100 * hashEntry.ctimes[0]) / 100) + ","; // 82.3MB // const content = JSON.stringify(Math.round(10 * hashEntry.ctimes[0]) / 10) + "," // 72.MB // const content = JSON.stringify(hashEntry.ctimes[0]) + "," // 162.9MB fs.writeSync(this.map[key].log, content); // this.map[key].log.write(content) // fs.writeFileSync( // path.join(dir, key + ".json"), // JSON.stringify( // [ // Math.round(10*hashEntry.ctimes[0])/10, // hashEntry.fnams[0] // ] // ) // + ",", // { "flag": "a" } // ) break default: console.log("Should not get to default in insert()!"); } } // The expected format is with time in the first dimension and pitch in the // second dimension of pts. It is assumed that pts is sorted // lexicographically. // maxOntimes: the max ontime of each piece of music in a dataset. match_hash_entries( pts, mode = "duples", tMin, tMax, pMin, pMax, maxOntimes, binSize, folder = __dirname, topN = 100 ) { let uninh = new Set(); // const bins = Math.ceil(maxOntimes[maxOntimes.length - 1] / binSize); let countBins = new Map(); // let countBin = new Array(bins).fill(0).map(() => { // return new Set() // }) pts = pts.slice(0, 80); const npts = pts.length; let nh = 0; let queLookupHashPointIdx = new Map(); // save matched query triples according to each countBin index. // Collect the topN matches. Will keep this sorted descending by setSize // property. let out = []; let jdx = 0; // Increment to populate out and throw away any unused entries. switch (mode) { case "duples": for (let i = 0; i < npts - 1; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts) { const v1 = pts[j]; const td = Math.round(100000*(v1[0] - v0[0]))/100000; const apd = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to make a hash entry. if (td > tMin && td < tMax && apd >= pMin && apd <= pMax) { // Make a hash entry, something like "±pdtd" const he = this.create_hash_entry( [v1[1] - v0[1], td], mode, v0[0] ); // console.log("he:", he) // Is there a match? const lookup = this.contains(he.hash); if (lookup !== undefined) { // There's a match! lookup.ctimes.forEach(function (ctime) { tInDset.push(ctime); tInQuery.push(he.ctimes[0]); }); } nh++; } // End whether to make a hash entry. if (td >= tMax) { j = npts - 1; } j++; } // End while. } // for (let i = 0; return { "nosHashes": nh, "uninosHashes": uninh.size, "countBins": countBins.map((value => { return value.size })) } //break case "triples": loop1: for (let i = 0; i < npts - 2; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts - 1) { const v1 = pts[j]; const td1 = Math.round(100000*(v1[0] - v0[0]))/100000; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = pts[k]; const td2 = Math.round(100000*(v2[0] - v1[0]))/100000; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, v0[0] ); if (fs.existsSync(path.join(folder, he.hash + ".json"))) { const lookupStr = fs.readFileSync( path.join(folder, he.hash + ".json"), "utf8" ).slice(0, -1); let lookup = JSON.parse("[" + lookupStr + "]"); lookup.forEach(function(item){ let tmp_fname = item[1]; let tmp_ontime = item[0]; // create a new countBin when a new music with quired hash appears. if(!countBins.has(tmp_fname)){ const bins = Math.ceil(maxOntimes[tmp_fname] / binSize); countBins.set(tmp_fname, new Array(bins).fill(0).map(() => {return new Set()})); } // Important line, and where other transformation operations // could be supported in future. let dif = tmp_ontime - he.ctimes[0]; if (dif >= 0 && dif <= maxOntimes[tmp_fname]){ var index_now = Math.floor(dif / binSize); var setArray = countBins.get(tmp_fname); var target = setArray[index_now]; target.add(he.hash); } }); } uninh.add(he.hash); nh++; if (nh > 5000) { break loop1 } } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { j = npts - 2; } j++; } // End j while. } // for (let i = 0; // Collect the topN matches. Will keep this sorted descending by setSize // property. for (let key of countBins.keys()){ const countBinsForPiece = countBins.get(key).map((value => { return value.size })); countBinsForPiece.forEach(function(count, idx){ if ( jdx === 0 || // Nothing in it. jdx < topN - 1 || // Still isn't full given value of topN. count > out[out.length - 1]["setSize"] // Bigger match than current minimum. ){ out[jdx] = { "winningPiece": key, "edge": idx*binSize, "setSize": count }; out.sort(function(a, b){ return b.setSize - a.setSize }); if (jdx < topN - 1){ jdx++; } } }); } break case "tripleIdx": loop1: for (let i = 0; i < npts - 2; i++) { const v0 = pts[i]; let j = i + 1; while (j < npts - 1) { const v1 = pts[j]; const td1 = Math.round(100000*(v1[0] - v0[0]))/100000; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = pts[k]; const td2 = Math.round(100000*(v2[0] - v1[0]))/100000; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], "triples", v0[0] ); if (fs.existsSync(path.join(folder, he.hash + ".json"))) { const lookupStr = fs.readFileSync( path.join(folder, he.hash + ".json"), "utf8" ).slice(0, -1); let lookup = JSON.parse("[" + lookupStr + "]"); lookup.forEach(function(item){ let tmp_fname = item[1]; let tmp_ontime = item[0]; // create a new countBin when a new music with quired hash appears. if(!countBins.has(tmp_fname)){ const bins = Math.ceil(maxOntimes[tmp_fname] / binSize); countBins.set(tmp_fname, new Array(bins).fill(0).map(() => {return new Set()})); queLookupHashPointIdx.set(tmp_fname, new Array(bins).fill(0).map(() => {return new Set()})); // Initialising } // Important line, and where other transformation operations // could be supported in future. let dif = tmp_ontime - he.ctimes[0]; if (dif >= 0 && dif <= maxOntimes[tmp_fname]){ let index_now = Math.floor(dif / binSize); let setArray = countBins.get(tmp_fname); let target = setArray[index_now]; if(!target.has(he.hash)){ target.add(he.hash); let queArray = queLookupHashPointIdx.get(tmp_fname); let tarQueBin = queArray[index_now]; tarQueBin.add([[i, j, k], item[2]]); } } }); } uninh.add(he.hash); nh++; if (nh > 5000) { break loop1 } } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { j = npts - 2; } j++; } // End j while. } // for (let i = 0; // Collect the topN matches. Will keep this sorted descending by setSize // property. for (let key of countBins.keys()){ const countBinsForPiece = countBins.get(key).map((value => { return value.size })); countBinsForPiece.forEach(function(count, idx){ if ( jdx === 0 || // Nothing in it. jdx < topN - 1 || // Still isn't full given value of topN. count > out[out.length - 1]["setSize"] // Bigger match than current minimum. ){ out[jdx] = { "winningPiece": key, "edge": idx*binSize, "setSize": count, "queLookupTriplets": Array.from(queLookupHashPointIdx.get(key)[idx]) }; out.sort(function(a, b){ return b.setSize - a.setSize }); if (jdx < topN - 1){ jdx++; } } }); } break default: console.log("Should not get to default in match_hash_entries() switch."); } return { "nosHashes": nh, "uninosHashes": uninh.size, "countBins": out } } // Return the count of unique matched hashes. // A query and a lookup piece will be regarded as a matched pair if their fingerprinting score > a threshold. match_query_lookup_piece( lookupPts, lookupFname, queryPts, mode = "triples", tMin, tMax, pMin, pMax, maxOntimes, binSize, topN = 100 ){ let lookupHashes = {}; let uninh = new Set(); // const bins = Math.ceil(maxOntimes[maxOntimes.length - 1] / binSize); let countBins = new Map(); // let countBin = new Array(bins).fill(0).map(() => { // return new Set() // }) // let pts = queryPts.slice(0, 80) // const npts = pts.length const npts = queryPts.length; let nh = 0; // Collect the topN matches. Will keep this sorted descending by setSize // property. let out = []; let jdx = 0; // Increment to populate out and throw away any unused entries. switch (mode) { case "triples": // Building hash table for the lookup piece. for (let i = 0; i < lookupPts.length - 2; i++) { const v0 = lookupPts[i]; let j = i + 1; while (j < lookupPts.length - 1) { const v1 = lookupPts[j]; const td1 = Math.round(100000*(v1[0] - v0[0]))/100000; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < lookupPts.length) { const v2 = lookupPts[k]; const td2 = Math.round(100000*(v2[0] - v1[0]))/100000; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, v0[0] ); if(he.hash in lookupHashes === false){ lookupHashes[he.hash] = []; } lookupHashes[he.hash].push(he.ctimes[0]); } // End whether to make a hash entry. if (td2 >= tMax) { k = lookupPts.length - 1; } k++; } // End k while. } if (td1 >= tMax) { j = lookupPts.length - 2; } j++; } } // Finding matched hashes. loop1: for (let i = 0; i < npts - 2; i++) { const v0 = queryPts[i]; let j = i + 1; while (j < npts - 1) { const v1 = queryPts[j]; const td1 = Math.round(100000*(v1[0] - v0[0]))/100000; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = queryPts[k]; const td2 = Math.round(100000*(v2[0] - v1[0]))/100000; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, v0[0] ); if (he.hash in lookupHashes) { let lookup = lookupHashes[he.hash]; let tmp_fname = lookupFname; lookup.forEach(function(item){ let tmp_ontime = item; // create a new countBin when a new music with quired hash appears. if(!countBins.has(tmp_fname)){ const bins = Math.ceil(maxOntimes / binSize); countBins.set(tmp_fname, new Array(bins).fill(0).map(() => {return new Set()})); } // Important line, and where other transformation operations // could be supported in future. let dif = tmp_ontime - he.ctimes[0]; if (dif >= 0 && dif <= maxOntimes){ var index_now = Math.floor(dif / binSize); var setArray = countBins.get(tmp_fname); var target = setArray[index_now]; target.add(he.hash); } }); } uninh.add(he.hash); nh++; if (nh > 5000) { break loop1 } } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { j = npts - 2; } j++; } // End j while. } // for (let i = 0; // Collect the topN matches. Will keep this sorted descending by setSize // property. for (let key of countBins.keys()){ const countBinsForPiece = countBins.get(key).map((value => { return value.size })); countBinsForPiece.forEach(function(count, idx){ if ( jdx === 0 || // Nothing in it. jdx < topN - 1 || // Still isn't full given value of topN. count > out[out.length - 1]["setSize"] // Bigger match than current minimum. ){ out[jdx] = { "winningPiece": key, "edge": idx*binSize, "setSize": count }; out.sort(function(a, b){ return b.setSize - a.setSize }); if (jdx < topN - 1){ jdx++; } } }); } break default: console.log("Should not get to default in match_hash_entries() switch."); } return { "nosHashes": nh, "uninosHashes": uninh.size, "countBins": out } } // Checking if a query matches a lookup piece by taking pre-processed hashes as input. match_precomputed_hashes( input_lookupHashes, input_queryHashes, maxOntimes, binSize, lookupFname, topN = 100, mode = "triples" ){ const lookupHashes = input_lookupHashes; const queryFull = input_queryHashes; const queryHashes = Object.keys(input_queryHashes); let uninh = new Set(); // const bins = Math.ceil(maxOntimes[maxOntimes.length - 1] / binSize); let countBins = new Map(); // let countBin = new Array(bins).fill(0).map(() => { // return new Set() // }) // let pts = queryPts.slice(0, 80) // const npts = pts.length let nh = 0; // Collect the topN matches. Will keep this sorted descending by setSize // property. let out = []; let jdx = 0; // Increment to populate out and throw away any unused entries. switch (mode) { case "triples": // Finding matched hashes. loop1: for (let i = 0; i < queryHashes.length; i ++){ if (queryHashes[i] in lookupHashes) { let lookup = lookupHashes[queryHashes[i]].ctimes; let tmp_fname = lookupFname; lookup.forEach(function(item){ let tmp_ontime = item; // create a new countBin when a new music with quired hash appears. if(!countBins.has(tmp_fname)){ const bins = Math.ceil(maxOntimes / binSize); countBins.set(tmp_fname, new Array(bins).fill(0).map(() => {return new Set()})); } // Important line, and where other transformation operations // could be supported in future. // Loop over ctimes in the query. const query_ctimes = queryFull[queryHashes[i]].ctimes; for(let idx_q_ctimes = 0; idx_q_ctimes < query_ctimes.length; idx_q_ctimes ++){ let dif = tmp_ontime - query_ctimes[idx_q_ctimes]; if (dif >= 0 && dif <= maxOntimes){ var index_now = Math.floor(dif / binSize); var setArray = countBins.get(tmp_fname); var target = setArray[index_now]; target.add(queryHashes[i]); } } }); } uninh.add(queryHashes[i]); nh++; if (nh > 5000) { break loop1 } } // Collect the topN matches. Will keep this sorted descending by setSize // property. for (let key of countBins.keys()){ const countBinsForPiece = countBins.get(key).map((value => { return value.size })); countBinsForPiece.forEach(function(count, idx){ if ( jdx === 0 || // Nothing in it. jdx < topN - 1 || // Still isn't full given value of topN. count > out[out.length - 1]["setSize"] // Bigger match than current minimum. ){ out[jdx] = { "winningPiece": key, "edge": idx*binSize, "setSize": count }; out.sort(function(a, b){ return b.setSize - a.setSize }); if (jdx < topN - 1){ jdx++; } } }); } break default: console.log("Should not get to default in match_hash_entries() switch."); } return { "nosHashes": nh, "uninosHashes": uninh.size, "countBins": out } } // Checking if a query matches a lookup piece by taking pre-processed lookup hashes as input, while process hashes for query online. match_precomputed_lookup_hashes( input_lookupHashes, queryPts, maxOntimes, binSize, lookupFname, tMin, tMax, pMin, pMax, topN = 100, mode = "triples" ){ const lookupHashes = input_lookupHashes; let uninh = new Set(); // const bins = Math.ceil(maxOntimes[maxOntimes.length - 1] / binSize); let countBins = new Map(); // let countBin = new Array(bins).fill(0).map(() => { // return new Set() // }) // let pts = queryPts.slice(0, 80) // const npts = pts.length const npts = queryPts.length; let nh = 0; // Collect the topN matches. Will keep this sorted descending by setSize // property. let out = []; let jdx = 0; // Increment to populate out and throw away any unused entries. switch (mode) { case "triples": // Finding matched hashes. // Finding matched hashes. loop1: for (let i = 0; i < npts - 2; i++) { const v0 = queryPts[i]; let j = i + 1; while (j < npts - 1) { const v1 = queryPts[j]; const td1 = Math.round(100000*(v1[0] - v0[0]))/100000; const apd1 = Math.abs(v1[1] - v0[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { let k = j + 1; while (k < npts) { const v2 = queryPts[k]; const td2 = Math.round(100000*(v2[0] - v1[0]))/100000; const apd2 = Math.abs(v2[1] - v1[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { const he = this.create_hash_entry( [v1[1] - v0[1], v2[1] - v1[1], td2 / td1], mode, v0[0] ); if (he.hash in lookupHashes) { let lookup = lookupHashes[he.hash].ctimes; let tmp_fname = lookupFname; lookup.forEach(function(item){ let tmp_ontime = item; // create a new countBin when a new music with quired hash appears. if(!countBins.has(tmp_fname)){ const bins = Math.ceil(maxOntimes / binSize); countBins.set(tmp_fname, new Array(bins).fill(0).map(() => {return new Set()})); }