/*
* This file is part of Toolkit.
*
* Toolkit is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Toolkit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
"use strict";
(function(w, TK){
function fast_draw_plinear(X, Y) {
var ret = [];
var i, len = X.length;
var dy = 0, x, y, tmp;
var accuracy = 20;
var c = 0;
if (len < 2) return "";
x = +X[0];
y = +Y[0];
ret.push("M", x.toFixed(2), ",", y.toFixed(2));
x = +X[1];
y = +Y[1];
dy = ((y - Y[0])*accuracy)|0;
for (i = 2; i < len; i++) {
tmp = ((Y[i] - y)*accuracy)|0;
if (tmp !== dy) {
ret.push("L", x.toFixed(2), ",", y.toFixed(2));
dy = tmp;
c++;
}
x = +X[i];
y = +Y[i];
}
ret.push("L", x.toFixed(2), ",", y.toFixed(2));
return ret.join("");
}
function draw_graph (graph, bands) {
var O = this.options;
var c = 0;
var end = this.range_x.get("basis") | 0;
var step = O.accuracy;
var over = O.oversampling;
var thres = O.threshold;
var x_px_to_val = this.range_x.px2val;
var y_val_to_px = this.range_y.val2px;
var i, j, k;
var x, y;
var pursue;
var diff;
var X = new Array(end / step);
for (i = 0; i < X.length; i++) {
X[i] = c;
c += step;
}
var Y = new Array(end / step);
var y;
for (i = 0; i < X.length; i++) {
x = x_px_to_val(X[i]);
y = 0.0;
for (j = 0; j < bands.length; j++) y += bands[j](x);
Y[i] = y_val_to_px(y);
var diff = Math.abs(Y[i] - Y[i-1]) >= thres;
if (i && over > 1 && (diff || pursue)) {
if (diff) pursue = true;
else if (!diff && pursue) pursue = false;
for (k = 1; k < over; k++) {
x = X[i-k] + ((step / over) * k);
X.splice(i, 0, x);
x = x_px_to_val(x);
y = 0.0;
for (j = 0; j < bands.length; j++) y += bands[j](x);
Y.splice(i, 0, y_val_to_px(y));
i++;
}
}
if (!isFinite(Y[i])) {
TK.warn("Singular filter in Equalizer.");
graph.set("dots", void(0));
return;
}
}
graph.set("dots", fast_draw_plinear(X, Y));
}
function invalidate_bands() {
this.invalid.bands = true;
this.trigger_draw();
}
function show_bands() {
var b = this.bands;
for (var i = 0; i < b.length; i ++) {
this.add_child(b[i]);
}
}
function hide_bands() {
var b = this.bands;
for (var i = 0; i < b.length; i ++) {
this.remove_child(b[i]);
}
}
TK.Equalizer = TK.class({
/**
* TK.Equalizer is a {@link TK.ResponseHandler}, utilizing {@link TK.EqBand}s instead of
* simple {@link TK.ResponseHandle}s.
*
* @property {Object} options
*
* @param {Number} [options.accuracy=1] - The distance between points on
* the x axis. Reduces CPU load in favour of accuracy and smoothness.
* @param {Array} [options.bands=[]] - A list of bands to add on init.
* @param {Boolean} [options.show_bands=true] - Show or hide all bands.
* @param {Number} [options.oversampling=5] - If slope of the curve is too
* steep, oversample n times in order to not miss e.g. notch filters.
* @param {Number} [options.threshold=5] - Steepness of slope to oversample,
* i.e. y pixels difference per x pixel
* @class TK.Equalizer
*
* @extends TK.ResponseHandler
*/
_class: "Equalizer",
Extends: TK.ResponseHandler,
_options: Object.assign(Object.create(TK.ResponseHandler.prototype._options), {
accuracy: "number",
oversampling: "number",
threshold: "number",
bands: "array",
show_bands: "boolean",
}),
options: {
accuracy: 1, // the distance between points of curves on the x axis
oversampling: 4, // if slope of the curve is too steep, oversample
// n times in order to not miss a notch filter
threshold: 10, // steepness of slope, i.e. amount of y pixels difference
bands: [], // list of bands to create on init
show_bands: true,
},
static_events: {
set_bands: function(value) {
if (this.bands.length) this.remove_bands();
this.add_bands(value);
},
set_show_bands: function(value) {
(value ? show_bands : hide_bands).call(this);
},
},
initialize: function (options) {
TK.ResponseHandler.prototype.initialize.call(this, options);
/**
* @member {Array} TK.Equalizer#bands - Array of {@link TK.EqBand} instances.
*/
this.bands = this.handles;
/**
* @member {HTMLDivElement} TK.Equalizer#element - The main DIV container.
* Has class <code>toolkit-equalizer</code>.
*/
TK.add_class(this.element, "toolkit-equalizer");
/**
* @member {SVGGroup} TK.Equalizer#_bands - The SVG group containing all the bands SVG elements.
* Has class <code>toolkit-eqbands</code>.
*/
this._bands = this._handles;
TK.add_class(this._bands, "toolkit-eqbands");
/**
* @member {TK.Graph} TK.Equalizer#baseline - The graph drawing the zero line.
* Has class <code>toolkit-baseline</code>
*/
this.baseline = this.add_graph({
range_x: this.range_x,
range_y: this.range_y,
container: this._bands,
dots: [{x: 20, y: 0}, {x: 20000, y: 0}],
"class": "toolkit-baseline"
});
this.add_bands(this.options.bands);
},
destroy: function () {
this.empty(); // Arne: ??? <- Markus: removes all graphs, defined in Chart
this._bands.remove();
TK.ResponseHandler.prototype.destroy.call(this);
},
redraw: function () {
var I = this.invalid;
var O = this.options;
TK.ResponseHandler.prototype.redraw.call(this);
if (I.validate("bands", "accuracy")) {
if (this.baseline) {
var f = [];
for (var i = 0; i < this.bands.length; i++) {
if (this.bands[i].get("active")) {
f.push(this.bands[i].filter.get_freq2gain());
}
}
draw_graph.call(this, this.baseline, f);
}
}
if (I.show_bands) {
I.show_bands = false;
if (O.show_bands) {
this._bands.style.removeProperty("display");
} else {
this._bands.style.display = "none";
}
}
},
resize: function () {
invalidate_bands.call(this);
TK.ResponseHandler.prototype.resize.call(this);
},
/**
* Add a new band to the equalizer. Options is an object containing
* options for the {@link TK.EqBand}
*
* @method TK.Equalizer#add_band
*
* @param {Object} [options={ }] - An object containing initial options for the {@link TK.EqBand}.
* @param {Object} [type=TK.EqBand] - A widget class to be used for the new band.
*
* @emits TK.Equalizer#bandadded
*/
add_band: function (options, type) {
var b;
type = type || TK.EqBand;
if (type.prototype.isPrototypeOf(options)) {
b = options;
} else {
options.container = this._bands;
if (options.range_x === void(0))
options.range_x = function () { return this.range_x; }.bind(this);
if (options.range_y === void(0))
options.range_y = function () { return this.range_y; }.bind(this);
if (options.range_z === void(0))
options.range_z = function () { return this.range_z; }.bind(this);
options.intersect = this.intersect.bind(this);
b = new type(options);
}
this.bands.push(b);
b.add_event("set", invalidate_bands.bind(this));
/**
* Is fired when a new band was added.
*
* @event TK.Equalizer#bandadded
*
* @param {TK.Band} band - The {@link TK.EqBand} which was added.
*/
this.fire_event("bandadded", b);
if (this.options.show_bands)
this.add_child(b);
invalidate_bands.call(this);
return b;
},
/**
* Add multiple new {@link TK.EqBand}s to the equalizer. Options is an array
* of objects containing options for the new instances of {@link TK.EqBand}
*
* @method TK.Equalizer#add_bands
*
* @param {Array<Object>} options - An array of options objects for the {@link TK.EqBand}.
* @param {Object} [type=TK.EqBand] - A widget class to be used for the new band.
*/
add_bands: function (bands, type) {
for (var i = 0; i < bands.length; i++)
this.add_band(bands[i], type);
},
/**
* Remove a band from the widget.
*
* @method TK.Equalizer#remove_handle
*
* @param {TK.EqBand} band - The {@link TK.EqBand} to remove.
*
* @emits TK.Equalizer#bandremoved
*/
remove_band: function (h) {
for (var i = 0; i < this.bands.length; i++) {
if (this.bands[i] === h) {
if (this.options.show_bands)
this.remove_child(h);
this.bands.splice(i, 1);
/**
* Is fired when a band was removed.
*
* @event TK.Equalizer#bandremoved
*
* @param {TK.EqBand} band - The {@link TK.EqBand} which was removed.
*/
this.fire_event("bandremoved", h);
h.destroy();
break;
}
}
},
/**
* Remove multiple {@link TK.EqBand} from the equalizer. Options is an array
* of {@link TK.EqBand} instances.
*
* @method TK.Equalizer#remove_bands
*
* @param {Array<TK.EqBand>} bands - An array of {@link TK.EqBand} instances.
*/
remove_bands: function () {
while (this.bands.length) {
this.remove_band(this.bands[0]);
}
this.bands = [];
/**
* Is fired when all bands are removed.
*
* @event TK.Equalizer#emptied
*/
this.fire_event("emptied");
invalidate_bands.call(this);
},
_draw_graph: draw_graph,
});
})(this, this.TK);