maia-hash
Version:
Hashing and fingerprinting functions supporting various applications by Music Artificial Intelligence Algorithms, Inc.
995 lines (937 loc) • 36 kB
JavaScript
// Imports
const fs = require("fs")
const path = require("path")
const mu = require("maia-util")
// import PointSet from './PointSet'
export default class HasherNoConcat {
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()}))
}
// 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
}
}
}