chromaprint-fixed
Version:
A JavaScript implementation of AcoustID Chromaprint
241 lines (212 loc) • 6.95 kB
JavaScript
// Generated by CoffeeScript 2.2.3
(function() {
var GRAYCODE, area, calculator, classifier, filter, integral, measure, quantizer;
if (this.chromaprint == null) {
this.chromaprint = {};
}
// Creating images comes down to choosing a 'shade of grey' from here.
GRAYCODE = [0, 1, 3, 2];
this.chromaprint.GRAYCODE = GRAYCODE;
// Images
// ======
// Chromaprint does its work on "images" - 2 dimensional arrays which are
// calculated from an input stream.
integral = function(image) {
var result;
result = [];
image.forEach(function(row, i) {
if (result[i] == null) {
result[i] = [];
}
return row.forEach(function(n, j) {
var ref, ref1, ref2, ref3, ref4, ref5;
return result[i][j] = n + ((ref = (ref1 = result[i]) != null ? ref1[j - 1] : void 0) != null ? ref : 0) + ((ref2 = (ref3 = result[i - 1]) != null ? ref3[j] : void 0) != null ? ref2 : 0) - ((ref4 = (ref5 = result[i - 1]) != null ? ref5[j - 1] : void 0) != null ? ref4 : 0);
});
});
return result;
};
area = function(image, x1, y1, x2, y2) {
var ref, ref1, result;
if (x1 == null) {
x1 = 0;
}
if (y1 == null) {
y1 = 0;
}
if (x2 == null) {
x2 = image.length;
}
if (y2 == null) {
y2 = (ref = image[x2]) != null ? ref.length : void 0;
}
result = (ref1 = image[x2]) != null ? ref1[y2] : void 0;
if (x2 < x1 || y2 < y1) {
return 0;
}
if (x1 > 0) {
result -= image[x1 - 1][y2];
if (y1 > 0) {
result += image[x1 - 1][y1 - 1];
}
}
if (y1 > 0) {
result -= image[x2][y1 - 1];
}
return result;
};
measure = function(image) {
var ref;
return {
h: image.length,
w: (ref = image[0].length) != null ? ref : 0
};
};
this.chromaprint.Image = {integral, area, measure};
// Filters
// =======
// A filter in chromaprint modifies a portion of an image. There are 5
// different filters available. The `filter` function takes as arguments:
// 1. the type of filter (0-4)
// 2. the y offset from where to apply the comparison
// 3. the x offset from where to apply the comparison
// 4. the width upon which to run the comparison
// 5. the height upon which to run the comparison
// 6. the actual comparison function (currently always subtraction, but log
// subtraction is also available.
filter = function(type, y, w, h, cmp) {
var f;
if (cmp == null) {
cmp = filter.compare.subtractLog;
}
f = function(img, x) {
return filter[type](img, x, y, w, h, cmp);
};
f.w = w;
f.h = h;
f.y = y;
return f;
};
filter[0] = function(img, x, y, w, h, cmp) { // oooooooooooo
var a, b;
a = area(img, x, y, x + w - 1, y + h - 1); // oooooooooooo
b = 0; // oooooooooooo
return cmp(a, b); // oooooooooooo
};
// oooooooooooo
// oooooooooooo
filter[1] = function(img, x, y, w, h, cmp) { // ............
var a, b, h2;
h2 = h / 2; // ............
a = area(img, x, y + h2, x + w - 1, y + h - 1); // ............
b = area(img, x, y, x + w - 1, y + h2 - 1); // oooooooooooo
return cmp(a, b); // oooooooooooo
};
// oooooooooooo
filter[2] = function(img, x, y, w, h, cmp) { // ......oooooo
var a, b, w2;
w2 = w / 2; // ......oooooo
a = area(img, x + w2, y, x + w - 1, y + h - 1); // ......oooooo
b = area(img, x, y, x + w2 - 1, y + y - 1); // ......oooooo
return cmp(a, b); // ......oooooo
};
// ......oooooo
filter[3] = function(img, x, y, w, h, cmp) { // ......oooooo
var a, b, h2, w2;
[w2, h2] = [
w / 2,
h / 2 // ......oooooo
];
a = area(img, x, y + h2, x + w2 - 1, y + h - 1) + area(img, x + w2, y, x + w - 1, y + h2 - 1); // ......oooooo // oooooo......
b = area(img, x, y, x + w2 - 1, y + h2 - 1) + area(img, x + w2, y + h2, x + w - 1, y + h - 1); // oooooo...... // oooooo......
return cmp(a, b);
};
filter[4] = function(img, x, y, w, h, cmp) { // ............
var a, b, h3;
h3 = h / 3; // ............
a = area(img, x, y + h3, x + w - 1, y + 2 * h3 - 1); // oooooooooooo
b = area(img, x, y, x + w - 1, y + h3 - 1) + area(img, x, y + 2 * h3, x + w - 1, y + h - 1); // oooooooooooo // ............
return cmp(a, b); // ............
};
filter[5] = function(img, x, y, w, h, cmp) { // ....oooo....
var a, b, w3;
w3 = w / 3; // ....oooo....
a = area(img, x + w3, y, x + 2 * w3 - 1, y + h - 1); // ....oooo....
b = area(img, x, y, x + w3 - 1, y + h - 1) + area(img, x + 2 * w3, y, x + w - 1, y + h(-1)); // ....oooo.... // ....oooo....
return cmp(a, b); // ....oooo....
};
filter.compare = {
subtract: function(a, b) {
return a - b;
},
subtractLog: function(a, b) {
return Math.log(1 + a) - Math.log(1 + b);
}
};
this.chromaprint.filter = filter;
// Quantizer
// =========
// Makes a very simple function to return 0, 1, 2, or 3 depending on where the
// value falls within the three values given.
quantizer = function(t0, t1, t2) {
return function(value) {
if (value < t1) {
if (value < t0) {
return 0;
}
return 1;
}
if (value < t2) {
return 2;
}
return 3;
};
};
this.chromaprint.quantizer = quantizer;
// Classifiers
// ===========
// classifier is a higher order function that makes a filter and a quantizer
// work together on an image.
classifier = function(coefficients) {
var f, fn, q;
f = filter(...coefficients.f);
q = quantizer(...coefficients.q);
fn = function(image, offset) {
return q(f(image, offset));
};
fn.filterWidth = f.w;
return fn;
};
this.chromaprint.classifier = classifier;
// Calculator
// ==========
// The calculator actually calculates the fingerprint of an image, by creating
// the right classifier, and passing the image into the resulting function in
// chunks.
calculator = function(...coefficients) {
var classifiers, max, subfingerprint;
classifiers = coefficients.map(function(cc) {
return classifier(cc);
});
max = classifiers.reduce((function(w, c) {
return Math.max(w, c.filterWidth);
}), 0);
subfingerprint = function(image, offset) {
var classify;
classify = function(b, classifier) {
return (b << 2) | GRAYCODE[classifier(image, offset)];
};
return classifiers.reduce(classify, 0);
};
return function(image) {
var img, k, length, offset, ref, results;
length = measure(image).h - max + 1;
img = integral(image);
results = [];
for (offset = k = 0, ref = length; (0 <= ref ? k < ref : k > ref); offset = 0 <= ref ? ++k : --k) {
results.push(subfingerprint(img, offset));
}
return results;
};
};
this.chromaprint.calculator = calculator;
}).call(this);