Source: widgets/dynamics.js

/*
 * 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 range_set(value, key) {
    this.range_x.set(key, value);
    this.range_y.set(key, value);
}
TK.Dynamics = TK.class({
    /**
     * TK.Dynamics are based on {@link TK.Chart} and display the characteristics of dynamic
     * processors. They are square widgets drawing a {@link TK.Grid} automatically based on
     * the range.
     *
     * @class TK.Dynamics
     * 
     * @extends TK.Chart
     * 
     * @property {Object} options
     * 
     * @param {Number} [options.min=-96] - Minimum decibels to display.
     * @param {Number} [options.max=24] - Maximum decibels to display.
     * @param {String} [options.scale="linear"] - Scale of the display, see {@link TK.Range} for details.
     * @param {String} [options.type=false] - Type of the dynamics: <code>compressor</code>, <code>expander</code>, <code>gate</code>, <code>limiter</code> or <code>false</code> to draw your own graph.
     * @param {Number} [options.threshold=0] - Threshold of the dynamics.
     * @param {Number} [options.ratio=1] - Ratio of the dynamics.
     * @param {Number} [options.makeup=0] - Makeup of the dynamics. This raises the whole graph after all other parameters are applied.
     * @param {Number} [options.range=0] - Range of the dynamics. Only used in type <code>expander</code>. The maximum gain reduction.
     * @param {Number} [options.gain=0] - Input gain of the dynamics.
     * @param {Number} [options.reference=0] - Input reference of the dynamics.
     * @param {Function} [options.grid_labels=function (val) { return val + (!val ? "dB":""); }] - Callback to format the labels of the {@link TK.Grid}.
     * @param {Number} [options.db_grid=12] - Draw a grid line every [n] decibels.
     */
    _class: "Dynamics",
    Extends: TK.Chart,
    _options: Object.assign(Object.create(TK.Chart.prototype._options), {
        size: "number", // deprecated, undocumented. Is set via CSS.
        min:  "number",
        max:  "number",
        scale: "string",
        type:  "string",
        threshold: "number",
        ratio:     "number",
        makeup:    "number",
        range:     "number",
        gain:      "number",
        reference: "number",
        grid_labels: "function",
        db_grid: "number",
    }),
    options: {
        db_grid: 12,
        min:     -96,
        max:     24,
        scale:   "linear",
        type:    false, 
        threshold: 0,
        ratio:     1,
        makeup:    0,
        range:     0,
        gain:      0,
        reference: 0,
        grid_labels: function (val) { return val + (!val ? "dB":""); }
    },
    static_events: {
        set_size: function(value) {
            TK.warn("using deprecated 'size' option");
            this.set("width", value);
            this.set("height", value);
        },
        set_min: range_set,
        set_max: range_set,
        set_scale: range_set,
    },
    initialize: function (options) {
        TK.Chart.prototype.initialize.call(this, options, true);
        var O = this.options;
        /**
         * @member {HTMLDivElement} TK.Dynamics#element - The main DIV container.
         *   Has class <code>toolkit-dynamics</code>.
         */
        TK.add_class(this.element, "toolkit-dynamics");
        this.set("scale", O.scale);
        if (O.size) this.set("size", O.size);
        this.set("min", O.min);
        this.set("max", O.max);
        /**
         * @member {TK.Graph} TK.Dynamics#steady - The graph drawing the zero line. Has class <code>toolkit-steady</code> 
         */
        this.steady = this.add_graph({
            dots: [{x:O.min, y:O.min},
                   {x:O.max, y:O.max}],
            "class": "toolkit-steady",
            mode: "line"
        });
    },
    
    redraw: function () {
        var O = this.options;
        var I = this.invalid;
        
        TK.Chart.prototype.redraw.call(this);

        if (I.validate("size", "min", "max", "scale")) {
            var grid_x = [];
            var grid_y = [];
            var min = this.range_x.get("min");
            var max = this.range_x.get("max");
            var step = O.db_grid;
            var cls;
            for (var i = min; i <= max; i += step) {
                cls = i ? "" : "toolkit-highlight";
                grid_x.push({
                    pos:     i,
                    label:   i === min ? "" : O.grid_labels(i),
                    "class": cls
                });
                grid_y.push({
                    pos:     i,
                    label:   i === min ? "" : O.grid_labels(i),
                    "class": cls
                });
            }
            if (this.grid) {
                this.grid.set("grid_x", grid_x);
                this.grid.set("grid_y", grid_y);
            }

            if (this.steady)
                this.steady.set("dots", [{x:O.min, y:O.min}, {x:O.max, y:O.max}]);
        }
        
        if (I.type) {
            if (O._last_type)
                TK.remove_class(this.element, "toolkit-" + O._last_type);
            TK.add_class(this.element, "toolkit-" + O.type);
        }

        if (I.validate("ratio", "threshold", "range", "makeup", "gain", "reference")) {
            this.draw_graph();
        }
    },

    resize: function() {
        var O = this.options;
        var E = this.element;
        var S = this.svg;

        /* bypass the Chart resize logic here */
        TK.Widget.prototype.resize.call(this);

        var tmp = TK.css_space(S, "border", "padding");
        var w = TK.inner_width(E) - tmp.left - tmp.right;
        var h = TK.inner_height(E) - tmp.top - tmp.bottom;

        var s = Math.min(h, w);

        if (s > 0 && s !== O._width) {
            this.set("_width", s);
            this.set("_height", s);
            this.range_x.set("basis", s);
            this.range_y.set("basis", s);
        }
    },
    
    draw_graph: function () {
        var O = this.options;
        if (O.type === false) return;
        if (!this.graph) {
            this.graph = this.add_graph({
                dots: [{x: O.min, y: O.min},
                       {x: O.max, y: O.max}]
            });
        }
        var curve = [];
        var range = O.range;
        var ratio = O.ratio;
        var thres = O.threshold;
        var gain = O.gain;
        var ref = O.reference;
        var makeup = O.makeup;
        var min = O.min;
        var max = O.max;
        var s;
        if (ref == 0)
        {
          s = 0;
        }
        else if (!isFinite(ratio))
        {
          s = ref;
        }
        else
        {
          s = (1 / (Math.max(ratio, 1.001) - 1)) * ratio * ref;
        }
        var l = 5; // estimated width of line. dirty workaround for
                   // keeping the line end out of sight in case
                   // salient point is outside the visible are
        switch (O.type) {
            case "compressor":
                // entry point
                curve.push({x: min - l,
                            y: min + makeup - gain + ref - l});
                // salient point
                curve.push({x: thres + gain - s,
                            y: thres + makeup - s + ref});
                // exit point
                if (isFinite(ratio) && ratio > 0) {
                    curve.push({x: max,
                                y: thres + makeup + (max - thres - gain) / ratio
                               });
                } else if (ratio === 0) {
                    curve.push({x: thres,
                                y: max
                               });
                } else {
                    curve.push({x: max,
                                y: thres + makeup
                               });
                }

                break;
            case "limiter":
                curve.push({x: min,
                            y: min + makeup - gain});
                curve.push({x: thres + gain,
                            y: thres + makeup});
                curve.push({x: max,
                            y: thres + makeup});
                break;
            case "gate":
                curve.push({x: thres,
                            y: min});
                curve.push({x: thres,
                            y: thres + makeup});
                curve.push({x: max,
                            y: max + makeup});
                break;
            case "expander":
                if (O.ratio !== 1) {
                    curve.push({x: min,
                                y: min + makeup + range});
                    
                    var y = (ratio * range + (ratio - 1) * thres) / (ratio - 1);
                    curve.push({x: y - range,
                                y: y + makeup});
                    curve.push({x: thres,
                                y: thres + makeup});
                }
                else
                    curve.push({x: min,
                                y: min + makeup});
                curve.push({x: max,
                            y: max + makeup});
                break;
            default:
                TK.warn("Unsupported type", O.type);
        }
        this.graph.set("dots", curve);
    },
    set: function (key, val) {
        if (key == "type")
            this.options._last_type = this.options.type;
        return TK.Chart.prototype.set.call(this, key, val);
    },
});
})(this, this.TK);