UNPKG

maia-hash

Version:

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

443 lines (410 loc) 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // Imports var fs = require("fs"); var path = require("path"); var mu = require("maia-util"); // import PointSet from './PointSet' var OntimePitchHasher = function () { function OntimePitchHasher(_mapPath) { _classCallCheck(this, OntimePitchHasher); if (_mapPath !== undefined) { this.map = require(_mapPath); } else { this.map = {}; } } _createClass(OntimePitchHasher, [{ key: "contains", value: function 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. }, { key: "create_hash_entries", value: function create_hash_entries(pts, cumuTime, fnam) { var mode = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "duples"; var insertMode = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : "increment and file with fnams"; var tMin = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0.1; var tMax = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 10; var pMin = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1; var pMax = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 12; var folder = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : __dirname; var npts = pts.length; // console.log("npts:", npts) var nh = 0; switch (mode) { case "duples": for (var i = 0; i < npts - 1; i++) { var v0 = pts[i]; var j = i + 1; while (j < npts) { var v1 = pts[j]; var td = v1[0] - v0[0]; var 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" var 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 (var _i = 0; _i < npts - 2; _i++) { var _v = pts[_i]; var _j = _i + 1; while (_j < npts - 1) { var _v2 = pts[_j]; var td1 = _v2[0] - _v[0]; var apd1 = Math.abs(_v2[1] - _v[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { var k = _j + 1; while (k < npts) { var v2 = pts[k]; var td2 = v2[0] - _v2[0]; var apd2 = Math.abs(v2[1] - _v2[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" var _he = this.create_hash_entry([_v2[1] - _v[1], v2[1] - _v2[1], td2 / td1], mode, cumuTime + _v[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; } }, { key: "create_hash_entry", value: function create_hash_entry(vals, mode, ctime, fnam, tMin, tMax) { var str = "", isInteger = void 0; switch (mode) { case "duples": // Sense-check pitch difference. var 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) { var 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]) var 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. var 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. }, { key: "get_piece_names", value: function get_piece_names(countBins, ctimes, fnams, binSize) { var topN = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 100; var out = []; // "out" contains the index of bin, // and it is sorted based on the corresponding number of hash entries contained in "hist". for (var 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(function (idx) { for (var _i2 = 0; _i2 < ctimes.length; _i2++) { if (idx * binSize <= ctimes[_i2]) { return { "winningPiece": fnams[_i2 - 1], "edge": idx * binSize, "count": countBins[idx] }; } } }); } }, { key: "insert", value: function insert(hashEntry) { var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "hash and lookup"; var dir = arguments[2]; var key = hashEntry.hash; var 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": if (lookup !== undefined) { this.map[key].increment++; } else { this.map[key] = { "increment": 1, "log": fs.openSync(path.join(dir, key + ".json"), "a" // {"flags": "a"} ) }; } var 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. }, { key: "match_hash_entries", value: function match_hash_entries(pts) { var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "duples"; var tMin = arguments[2]; var tMax = arguments[3]; var pMin = arguments[4]; var pMax = arguments[5]; var ctimes = arguments[6]; var _this = this; var binSize = arguments[7]; var folder = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : __dirname; var uninh = new Set(); var bins = Math.ceil(ctimes[ctimes.length - 1] / binSize); var countBins = new Array(bins).fill(0).map(function () { return new Set(); }); pts = pts.slice(0, 80); var npts = pts.length; var nh = 0; switch (mode) { case "duples": for (var i = 0; i < npts - 1; i++) { var v0 = pts[i]; var j = i + 1; while (j < npts) { var v1 = pts[j]; var td = v1[0] - v0[0]; var 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) { (function () { // Make a hash entry, something like "±pdtd" var he = _this.create_hash_entry([v1[1] - v0[1], td], mode, v0[0]); // console.log("he:", he) // Is there a match? var 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 (var _i3 = 0; _i3 < npts - 2; _i3++) { var _v3 = pts[_i3]; var _j2 = _i3 + 1; while (_j2 < npts - 1) { var _v4 = pts[_j2]; var td1 = _v4[0] - _v3[0]; var apd1 = Math.abs(_v4[1] - _v3[1]); // console.log("i:", i, "j:", j) // Decide whether to proceed to v1 and v2. if (td1 > tMin && td1 < tMax && apd1 >= pMin && apd1 <= pMax) { var k = _j2 + 1; while (k < npts) { var v2 = pts[k]; var td2 = v2[0] - _v4[0]; var apd2 = Math.abs(v2[1] - _v4[1]); // console.log("j:", j, "k:", k) // Decide whether to make a hash entry. if (td2 > tMin && td2 < tMax && apd2 >= pMin && apd2 <= pMax) { var _ret2 = function () { var he = _this.create_hash_entry([_v4[1] - _v3[1], v2[1] - _v4[1], td2 / td1], mode, _v3[0]); if (fs.existsSync(path.join(folder, he.hash + ".json"))) { var lookupStr = fs.readFileSync(path.join(folder, he.hash + ".json"), "utf8").slice(0, -1); var lookup = JSON.parse("[" + lookupStr + "]"); lookup.forEach(function (value) { var 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) { return "break|loop1"; } }(); if (_ret2 === "break|loop1") break loop1; } // End whether to make a hash entry. if (td2 >= tMax) { k = npts - 1; } k++; } // End k while. } if (td1 >= tMax) { _j2 = npts - 2; } _j2++; } // 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(function (value) { return value.size; }) }; } }]); return OntimePitchHasher; }(); exports.default = OntimePitchHasher;