<commit_before>"use strict"

module.exports = {
	"rules": {
		"at-rule-empty-line-before": "always",
		"at-rule-name-case": "lower",
		"at-rule-name-newline-after": "always",
		"at-rule-name-space-after": "always",
		"at-rule-no-unknown": true,
		"at-rule-no-vendor-prefix": true,
		"at-rule-semicolon-newline-after": "always",
		"at-rule-semicolon-space-before": "never",
		"block-closing-brace-empty-line-before":"never",
		"block-closing-brace-newline-after": "always",
		"block-closing-brace-newline-before": "always",
		"block-closing-brace-space-after": "never-multi-line",
		"block-closing-brace-space-before": "never",
		"block-no-empty": true,
		"block-opening-brace-newline-after": "always",
		"block-opening-brace-newline-before": "never-multi-line",
		"block-opening-brace-space-after": "never",
		"block-opening-brace-space-before": "always",
		"color-hex-case": "lower",
		"color-hex-length": "short",
		"color-named": "always-where-possible",
		"color-no-hex": false,
		"color-no-invalid-hex": true,
		"comment-empty-line-before": "always",
		"comment-no-empty": true,
		"comment-whitespace-inside": "always",
		"custom-property-empty-line-before": "never",
		"declaration-bang-space-after": "never",
		"declaration-bang-space-before": "always",
		"declaration-block-no-duplicate-properties": true,
		"declaration-block-no-redundant-longhand-properties": true,
		"declaration-block-no-shorthand-property-overrides": true,
		"declaration-block-semicolon-newline-after": "always",
		"declaration-block-semicolon-newline-before": "never-multi-line",
		"declaration-block-semicolon-space-after": "never-single-line",
		"declaration-block-semicolon-space-before": "never",
		"declaration-block-single-line-max-declarations": 1,
		"declaration-block-trailing-semicolon": "always",
		"declaration-colon-newline-after": "always-multi-line",
		"declaration-colon-space-after": "always",
		"declaration-colon-space-before": "never",
		"declaration-empty-line-before": "never",
		"declaration-no-important": true,
		"font-family-name-quotes": "always-where-recommended",
		"font-family-no-duplicate-names": true,
		"font-weight-notation": "named-where-possible",
		"function-calc-no-unspaced-operator": true,
		"function-comma-newline-after": "never-multi-line",
		"function-comma-newline-before": "never-multi-line",
		"function-comma-space-after": "always",
		"function-comma-space-before": "never",
		"function-linear-gradient-no-nonstandard-direction": true,
		"function-max-empty-lines": 0,
		"function-name-case": "lower",
		"function-parentheses-newline-inside": "never-multi-line",
		"function-parentheses-space-inside": "never",
		"function-url-no-scheme-relative": true,
		"function-url-quotes": "never",
		"function-whitespace-after": "always",
		"indentation": "tab",
		"keyframe-declaration-no-important": true,
		"length-zero-no-unit": true,
		"max-empty-lines": 1,
		"max-nesting-depth": 2,
		"media-feature-colon-space-after": "always",
		"media-feature-colon-space-before": "never",
		"media-feature-name-case": "lower",
		"media-feature-name-no-unknown": true,
		"media-feature-name-no-vendor-prefix": true,
		"media-feature-parentheses-space-inside": "never",
		"media-feature-range-operator-space-after": "always",
		"media-feature-range-operator-space-before": "always",
		"media-query-list-comma-newline-after": "always",
		"media-query-list-comma-newline-before": "never-multi-line",
		"media-query-list-comma-space-after": "never",
		"media-query-list-comma-space-before": "never",
		"no-descending-specificity": true,
		"no-duplicate-selectors": true,
		"no-empty-source": true,
		"no-eol-whitespace": true,
		"no-extra-semicolons": true,
		"no-invalid-double-slash-comments": true,
		"no-missing-end-of-source-newline": true,
		"no-unknown-animations": true,
		"number-leading-zero": "never",
		"number-max-precision": 10,
		"number-no-trailing-zeros": true,
		"property-case": "lower",
		"property-no-unknown": true,
		"property-no-vendor-prefix": true,
		"rule-empty-line-before": "always-multi-line",
		"selector-attribute-brackets-space-inside": "never",
		"selector-attribute-operator-space-after": "never",
		"selector-attribute-operator-space-before": "never",
		"selector-attribute-quotes": "never",
		"selector-combinator-space-after": "always",
		"selector-combinator-space-before": "always",
		"selector-descendant-combinator-no-non-space": true,
		"selector-list-comma-newline-after": "always",
		"selector-list-comma-newline-before": "never-multi-line",
		"selector-list-comma-space-after": "never",
		"selector-list-comma-space-before": "never",
		"selector-max-empty-lines": 0,
		"selector-max-compound-selectors": 2,
		"selector-max-specificity": "0,2,0",
		"selector-nested-pattern": "^&:(?:hover|focus)$",
		"selector-no-qualifying-type": true,
		"selector-no-vendor-prefix": true,
		"selector-pseudo-class-case": "lower",
		"selector-pseudo-class-no-unknown": true,
		"selector-pseudo-class-parentheses-space-inside": "never",
		"selector-pseudo-element-case": "lower",
		"selector-pseudo-element-colon-notation": "single",
		"selector-pseudo-element-no-unknown": true,
		"selector-type-case": "lower"|"upper",
		"selector-type-no-unknown": true,
		"shorthand-property-no-redundant-values": true,
		"string-no-newline": true,
		"string-quotes": "single",
		"time-min-milliseconds": 150,
		"unit-case": "lower",
		"unit-no-unknown": true,
		"value-keyword-case": "lower",
		"value-list-comma-newline-after": "never-multi-line",
		"value-list-comma-newline-before": "never-multi-line",
		"value-list-comma-space-after": "always",
		"value-list-comma-space-before": "never",
		"value-list-max-empty-lines": 0,
		"value-no-vendor-prefix": true
	}
}
<commit_msg>add sane defaults for rules<commit_after>"use strict"

module.exports = {
	"rules": {
		"at-rule-empty-line-before": ["always", {
			"except": ["after-same-name"]
		}],
		"at-rule-name-case": "lower",
		"at-rule-name-space-after": "always",
		"at-rule-no-unknown": true,
		"at-rule-no-vendor-prefix": true,
		"at-rule-semicolon-newline-after": "always",
		"at-rule-semicolon-space-before": "never",
		"block-closing-brace-empty-line-before":"never",
		"block-closing-brace-newline-after": "always",
		"block-closing-brace-newline-before": "always",
		"block-no-empty": true,
		"block-opening-brace-newline-after": "always",
		"block-opening-brace-newline-before": "never-single-line",
		"block-opening-brace-space-before": "always",
		"color-hex-case": "lower",
		"color-hex-length": "short",
		"color-named": "always-where-possible",
		"color-no-hex": null,
		"color-no-invalid-hex": true,
		"comment-empty-line-before": "always",
		"comment-no-empty": true,
		"comment-whitespace-inside": "always",
		"custom-property-empty-line-before": "never",
		"declaration-bang-space-after": "never",
		"declaration-bang-space-before": "always",
		"declaration-block-no-duplicate-properties": true,
		"declaration-block-no-redundant-longhand-properties": true,
		"declaration-block-no-shorthand-property-overrides": true,
		"declaration-block-semicolon-newline-after": "always",
		"declaration-block-semicolon-newline-before": "never-multi-line",
		"declaration-block-semicolon-space-after": "never-single-line",
		"declaration-block-semicolon-space-before": "never",
		"declaration-block-single-line-max-declarations": 1,
		"declaration-block-trailing-semicolon": "always",
		"declaration-colon-space-after": "always",
		"declaration-colon-space-before": "never",
		"declaration-empty-line-before": "never",
		"declaration-no-important": true,
		"font-family-name-quotes": "always-unless-keyword",
		"font-family-no-duplicate-names": true,
		"font-weight-notation": ["named-where-possible", {
			"ignore": ["relative"]
		}],
		"function-calc-no-unspaced-operator": true,
		"function-comma-newline-after": "never-multi-line",
		"function-comma-newline-before": "never-multi-line",
		"function-comma-space-after": "always",
		"function-comma-space-before": "never",
		"function-linear-gradient-no-nonstandard-direction": true,
		"function-max-empty-lines": 0,
		"function-name-case": "lower",
		"function-parentheses-newline-inside": "never-multi-line",
		"function-parentheses-space-inside": "never",
		"function-url-no-scheme-relative": true,
		"function-url-quotes": "never",
		"function-whitespace-after": "always",
		"indentation": "tab",
		"keyframe-declaration-no-important": true,
		"length-zero-no-unit": true,
		"max-empty-lines": 1,
		"max-nesting-depth": 2,
		"media-feature-colon-space-after": "always",
		"media-feature-colon-space-before": "never",
		"media-feature-name-case": "lower",
		"media-feature-name-no-unknown": true,
		"media-feature-name-no-vendor-prefix": true,
		"media-feature-parentheses-space-inside": "never",
		"media-feature-range-operator-space-after": "always",
		"media-feature-range-operator-space-before": "always",
		"media-query-list-comma-newline-after": "always",
		"media-query-list-comma-newline-before": "never-multi-line",
		"media-query-list-comma-space-after": "never",
		"media-query-list-comma-space-before": "never",
		"no-descending-specificity": true,
		"no-duplicate-selectors": true,
		"no-empty-source": true,
		"no-eol-whitespace": true,
		"no-extra-semicolons": true,
		"no-invalid-double-slash-comments": true,
		"no-missing-end-of-source-newline": true,
		"no-unknown-animations": true,
		"number-leading-zero": "never",
		"number-max-precision": 10,
		"number-no-trailing-zeros": true,
		"property-case": "lower",
		"property-no-unknown": true,
		"property-no-vendor-prefix": true,
		"rule-empty-line-before": "always-multi-line",
		"selector-attribute-brackets-space-inside": "never",
		"selector-attribute-operator-space-after": "never",
		"selector-attribute-operator-space-before": "never",
		"selector-attribute-quotes": "never",
		"selector-combinator-space-after": "always",
		"selector-combinator-space-before": "always",
		"selector-descendant-combinator-no-non-space": true,
		"selector-list-comma-newline-after": "always",
		"selector-list-comma-newline-before": "never-multi-line",
		"selector-list-comma-space-after": null,
		"selector-list-comma-space-before": "never",
		"selector-max-empty-lines": 0,
		"selector-max-compound-selectors": 2,
		"selector-max-specificity": "0,2,0",
		"selector-nested-pattern": "^&:(?:hover|focus)$",
		"selector-no-qualifying-type": true,
		"selector-no-vendor-prefix": true,
		"selector-pseudo-class-case": "lower",
		"selector-pseudo-class-no-unknown": true,
		"selector-pseudo-class-parentheses-space-inside": "never",
		"selector-pseudo-element-case": "lower",
		"selector-pseudo-element-colon-notation": "single",
		"selector-pseudo-element-no-unknown": true,
		"selector-type-case": "lower",
		"selector-type-no-unknown": true,
		"shorthand-property-no-redundant-values": true,
		"string-no-newline": true,
		"string-quotes": "single",
		"time-min-milliseconds": 150,
		"unit-case": "lower",
		"unit-no-unknown": true,
		"value-keyword-case": "lower",
		"value-list-comma-newline-before": "never-multi-line",
		"value-list-comma-space-after": "always-single-line",
		"value-list-comma-space-before": "never",
		"value-list-max-empty-lines": 0,
		"value-no-vendor-prefix": true
	}
}
<|endoftext|>


<commit_before>/*jslint node: true, vars: true, indent: 4 */
(function (module) {
    "use strict";

    var ua = require('mobile-agent'),
        constants = {
            TRAILING_SCRIPTS: 'trailingScripts',
            STYLESHEETS: 'stylesheets',
            SCRIPTS: 'scripts',
            TEMPLATE_FILES: 'templateFiles'
        },
        AGENT_TYPES = ['Android', 'Browser', 'iOS', 'iPad', 'iPhone', 'Mac', 'Mobile', 'webOS', 'Windows', 'Cordova', 'CordovaDefault'],
        cache = {
            templates: {},
            files: {}
        },
        HTTP_NOT_FOUND = 404,
        path = require("path"),
        hbars = require("handlebars"),
        util = require("./lib/util"),
        locate = util.locate,
        fs = require("fs"),
        async = require('async');

    function locateSync(dirs, file, idx) {
        var i = idx || 0;

        if (i >= dirs.length) {
            throw new Error("file " + file + " not found in " + dirs);
        }

        var qfile = path.join(dirs[i], file);

        if (fs.existsSync(qfile)) {
            return qfile;
        } else {
            return locateSync(dirs, file, i + 1);
        }
    }

    function merge(masterAssetData, newAssetData) {

        var assetKeys = Object.keys(newAssetData);
        var i = assetKeys.length;
        var propName;

        while (i--) {
            propName = assetKeys[i];
            var newAssetDataValue = newAssetData[propName];

            //console.log('propName: ', propName);

            if (Array.isArray(newAssetDataValue)) {
                if (masterAssetData[propName]) {
                    masterAssetData[propName] = masterAssetData[propName].concat(newAssetDataValue);
                } else {
                    masterAssetData[propName] = newAssetDataValue;
                }

            } else {

                if (typeof newAssetDataValue === 'object') {

                    var newAssetDataValueKeys = Object.keys(newAssetDataValue);
                    var j = newAssetDataValueKeys.length;
                    var prop;

                    while (j--) {
                        prop = newAssetDataValueKeys[j];
                        var newAssetDataObjectValue = newAssetDataValue[prop];

                        if (Array.isArray(newAssetDataObjectValue)) {
                            if (masterAssetData[propName] && masterAssetData[propName][prop]) {
                                masterAssetData[propName][prop] = masterAssetData[propName][prop].concat(newAssetDataObjectValue);
                            } else {
                                masterAssetData[propName][prop] = newAssetDataObjectValue;
                            }
                        } else {
                            masterAssetData[propName][prop] = newAssetDataObjectValue;
                        }
                    }
                } else {
                    masterAssetData[propName] = newAssetDataValue;
                }
            }
        }
    }

    function _filterByAgent(agentInfo, propName, assets, filtered) {
        var agentKeys = agentInfo.agentTypes;
        var agentType;

        assets.forEach(function (asset) {
            if (typeof asset === 'string') {
                if (filtered[propName]) {
                    filtered[propName].push(asset);
                } else {
                    filtered[propName] = [asset];
                }
            } else if (asset.agents) {
                var i = agentKeys.length;
                while (i--) {
                    agentType = agentKeys[i];
                    if (asset.agents.indexOf(agentType.toLowerCase()) >= 0 || asset.agents.indexOf(agentType) >= 0) {
                        if (filtered[propName]) {
                            filtered[propName].push(asset.name);
                        } else {
                            filtered[propName] = [asset];
                        }
                    }
                }
            }
        });
    }

    function filter(agentInfo, defaults) {
        var filtered = {};
        var defaultKeys = Object.keys(defaults);
        var i = defaultKeys.length;
        var propName;
        var propValue;

        while (i--) {
            propName = defaultKeys[i];
            propValue = defaults[propName];
            switch (propName) {
                case constants.TRAILING_SCRIPTS:
                case constants.STYLESHEETS:
                case constants.SCRIPTS:
                    _filterByAgent(agentInfo, propName, propValue, filtered);
                    break;
                default:
                    filtered[propName] = propValue;
                    break;
            }
        }

        return filtered;

    }

    function _getUserAgentHash(req) {
        var agent = ua(req.headers['user-agent']);
        var hash = '';
        var agentTypes = [];

        AGENT_TYPES.forEach(function (type) {
            var newVal = '0';
            if (agent[type]) {
                agentTypes.push(type);
                newVal = '1';
            } else if (req.query['agent'] === type) {
                agentTypes.push(req.query['agent']);
                newVal = '1';
            }

            hash = hash + newVal;
        });

        return {
            hash: hash,
            agentTypes: agentTypes
        };
    }

    function getCachedModule(req, namespace) {
        //console.log('getCachedModule: ', namespace);
        if(!namespace) {
            return undefined;
        }
        console.log("originalUrl: " + req.originalUrl);
        var agentInfo = _getUserAgentHash(req);
        var userAgentHash = agentInfo.hash;
        if (namespace) {
            if (namespace && cache[namespace] && cache[namespace][userAgentHash]) {
                return cache[namespace][userAgentHash];
            } else {
                return undefined;
            }
        }
    }

    function getModule(req, module) {
        //console.log('getModule: ');
        //console.log("originalUrl: " + req.originalUrl);
        var agentInfo = _getUserAgentHash(req);
        var userAgentHash = agentInfo.hash;

        if (cache[userAgentHash]) {
            console.log("cached userAgentHash: " + userAgentHash);
            return cache[userAgentHash];
        } else {
            var mod = filter(agentInfo, module);
            cache[userAgentHash] = mod;
            console.log("NOT cached userAgentHash: " + userAgentHash);

            return mod;
        }
    } // 01110010001

    function setModule(req, module, namespace) {
        //console.log('setModule: ', namespace);

        var agentInfo = _getUserAgentHash(req);
        var userAgentHash = agentInfo.hash;
        var mod = filter(agentInfo, module);
        if (namespace) {
            if(!cache[namespace]) {
                cache[namespace] = {};
            }
            if(!cache[namespace][userAgentHash]) {
                cache[namespace][userAgentHash] = mod;
            }
        } else {
            if (cache[userAgentHash]) {
                cache[userAgentHash] = mod;
            } else {
                cache[userAgentHash] = mod;
            }
        }
    }

    function load(cache, file, cb, debug) {
        if (Object.prototype.toString.call(file) === '[object Function]') {
            return file(cb);
        }

        var cached = cache.files[file];

        if (cached) {
            return cb(undefined, cached);
        }

        fs.readFile(file, "utf-8", function (err, content) {
            if (err) {
                return cb(err);
            }

            cache.files[file] = content;

            return cb(undefined, content);
        });
    }

    function compileHtml(mod, file, statusCode, debug, cb) {
        var cached = cache.templates[file];

        if (cached) {
            return cb(undefined, mod, cached, statusCode);
        }

        return locate(mod["public"], file, function (err, qfile) {
            if (err) {
                if (statusCode === HTTP_NOT_FOUND) {
                    return (cb)?cb(err):undefined;
                }

                return compileHtml(mod, "/404.html", cb, HTTP_NOT_FOUND, debug);
            }
            return load(cache, qfile, function (err, content) {
                if (err) {
                    return cb(err);
                }

                var template = hbars.compile(content);

                cache.templates[file] = template;

                return cb(undefined, mod, template, statusCode);
            }, debug);
        });
    }

    function compileSite(mod, debug, cb) {
        var htmlFiles = mod.htmlFiles,
            rawHtml = [],
            idx = 0,
            templateFiles = mod.templateFiles,
            templateHtml = [],
            file;

        function loadHtmlFiles(cb) {
            var pos = idx++;
            async.each(htmlFiles, function (file, callback) {
                load(cache, file, function (err, content) {
                    if (err) {
                        return callback(err);
                    }

                    rawHtml[pos] = content;
                    return callback();
                }, debug);
            }, cb);
        }

        function loadTemplateFiles(cb) {
            async.each(templateFiles, function (file, callback) {
                var name = path.basename(file, '.html');

                load(cache, file, function (err, content) {
                    if (err) {
                        return callback(err);
                    }

                    templateHtml.push({
                        name: name,
                        content: content
                    });
                    return callback();
                }, debug);
            }, cb);
        }

        if (!mod.compileSite) {
            async.parallel([
                loadHtmlFiles,
                loadTemplateFiles
            ], function (err) {
                if (err) {
                    debug.error(err);
                }
                mod.htmlFiles = rawHtml;
                mod.templateFiles = templateHtml;
                mod.compileSite = true;
                cb();
            });
        } else {
            cb();
        }
    }

    module.exports = {
        merge: merge,
        filter: filter,
        getCachedModule: getCachedModule,
        getModule: getModule,
        setModule: setModule,
        compileSite: compileSite,
        compileHtml: compileHtml,
        locateSync: locateSync
    };

})(module);<commit_msg>add chrome agent.<commit_after>/*jslint node: true, vars: true, indent: 4 */
(function (module) {
    "use strict";

    var ua = require('mobile-agent'),
        constants = {
            TRAILING_SCRIPTS: 'trailingScripts',
            STYLESHEETS: 'stylesheets',
            SCRIPTS: 'scripts',
            TEMPLATE_FILES: 'templateFiles'
        },
        AGENT_TYPES = ['Android', 'Browser', 'iOS', 'iPad', 'iPhone', 'Mac', 'Mobile', 'webOS', 'Windows', 'Cordova', 'CordovaDefault', 'Chrome'],
        cache = {
            templates: {},
            files: {}
        },
        HTTP_NOT_FOUND = 404,
        path = require("path"),
        hbars = require("handlebars"),
        util = require("./lib/util"),
        locate = util.locate,
        fs = require("fs"),
        async = require('async');

    function locateSync(dirs, file, idx) {
        var i = idx || 0;

        if (i >= dirs.length) {
            throw new Error("file " + file + " not found in " + dirs);
        }

        var qfile = path.join(dirs[i], file);

        if (fs.existsSync(qfile)) {
            return qfile;
        } else {
            return locateSync(dirs, file, i + 1);
        }
    }

    function merge(masterAssetData, newAssetData) {

        var assetKeys = Object.keys(newAssetData);
        var i = assetKeys.length;
        var propName;

        while (i--) {
            propName = assetKeys[i];
            var newAssetDataValue = newAssetData[propName];

            //console.log('propName: ', propName);

            if (Array.isArray(newAssetDataValue)) {
                if (masterAssetData[propName]) {
                    masterAssetData[propName] = masterAssetData[propName].concat(newAssetDataValue);
                } else {
                    masterAssetData[propName] = newAssetDataValue;
                }

            } else {

                if (typeof newAssetDataValue === 'object') {

                    var newAssetDataValueKeys = Object.keys(newAssetDataValue);
                    var j = newAssetDataValueKeys.length;
                    var prop;

                    while (j--) {
                        prop = newAssetDataValueKeys[j];
                        var newAssetDataObjectValue = newAssetDataValue[prop];

                        if (Array.isArray(newAssetDataObjectValue)) {
                            if (masterAssetData[propName] && masterAssetData[propName][prop]) {
                                masterAssetData[propName][prop] = masterAssetData[propName][prop].concat(newAssetDataObjectValue);
                            } else {
                                masterAssetData[propName][prop] = newAssetDataObjectValue;
                            }
                        } else {
                            masterAssetData[propName][prop] = newAssetDataObjectValue;
                        }
                    }
                } else {
                    masterAssetData[propName] = newAssetDataValue;
                }
            }
        }
    }

    function _filterByAgent(agentInfo, propName, assets, filtered) {
        var agentKeys = agentInfo.agentTypes;
        var agentType;

        assets.forEach(function (asset) {
            if (typeof asset === 'string') {
                if (filtered[propName]) {
                    filtered[propName].push(asset);
                } else {
                    filtered[propName] = [asset];
                }
            } else if (asset.agents) {
                var i = agentKeys.length;
                while (i--) {
                    agentType = agentKeys[i];
                    if (asset.agents.indexOf(agentType.toLowerCase()) >= 0 || asset.agents.indexOf(agentType) >= 0) {
                        if (filtered[propName]) {
                            filtered[propName].push(asset.name);
                        } else {
                            filtered[propName] = [asset];
                        }
                    }
                }
            }
        });
    }

    function filter(agentInfo, defaults) {
        var filtered = {};
        var defaultKeys = Object.keys(defaults);
        var i = defaultKeys.length;
        var propName;
        var propValue;

        while (i--) {
            propName = defaultKeys[i];
            propValue = defaults[propName];
            switch (propName) {
                case constants.TRAILING_SCRIPTS:
                case constants.STYLESHEETS:
                case constants.SCRIPTS:
                    _filterByAgent(agentInfo, propName, propValue, filtered);
                    break;
                default:
                    filtered[propName] = propValue;
                    break;
            }
        }

        return filtered;

    }

    function _getUserAgentHash(req) {
        var agent = ua(req.headers['user-agent']);
        var hash = '';
        var agentTypes = [];

        AGENT_TYPES.forEach(function (type) {
            var newVal = '0';
            if (agent[type]) {
                agentTypes.push(type);
                newVal = '1';
            } else if (req.query['agent'] === type) {
                agentTypes.push(req.query['agent']);
                newVal = '1';
            } else if (type === 'Chrome' &&agent.Browser.name == 'chrome' ) {
                agentTypes.push(type);
                newVal = '1';
            }

            hash = hash + newVal;
        });

        return {
            hash: hash,
            agentTypes: agentTypes
        };
    }

    function getCachedModule(req, namespace) {
        //console.log('getCachedModule: ', namespace);
        if(!namespace) {
            return undefined;
        }
        console.log("originalUrl: " + req.originalUrl);
        var agentInfo = _getUserAgentHash(req);
        var userAgentHash = agentInfo.hash;
        if (namespace) {
            if (namespace && cache[namespace] && cache[namespace][userAgentHash]) {
                return cache[namespace][userAgentHash];
            } else {
                return undefined;
            }
        }
    }

    function getModule(req, module) {
        //console.log('getModule: ');
        //console.log("originalUrl: " + req.originalUrl);
        var agentInfo = _getUserAgentHash(req);
        var userAgentHash = agentInfo.hash;

        if (cache[userAgentHash]) {
            console.log("cached userAgentHash: " + userAgentHash);
            return cache[userAgentHash];
        } else {
            var mod = filter(agentInfo, module);
            cache[userAgentHash] = mod;
            console.log("NOT cached userAgentHash: " + userAgentHash);

            return mod;
        }
    } // 01110010001

    function setModule(req, module, namespace) {
        //console.log('setModule: ', namespace);

        var agentInfo = _getUserAgentHash(req);
        var userAgentHash = agentInfo.hash;
        var mod = filter(agentInfo, module);
        if (namespace) {
            if(!cache[namespace]) {
                cache[namespace] = {};
            }
            if(!cache[namespace][userAgentHash]) {
                cache[namespace][userAgentHash] = mod;
            }
        } else {
            if (cache[userAgentHash]) {
                cache[userAgentHash] = mod;
            } else {
                cache[userAgentHash] = mod;
            }
        }
    }

    function load(cache, file, cb, debug) {
        if (Object.prototype.toString.call(file) === '[object Function]') {
            return file(cb);
        }

        var cached = cache.files[file];

        if (cached) {
            return cb(undefined, cached);
        }

        fs.readFile(file, "utf-8", function (err, content) {
            if (err) {
                return cb(err);
            }

            cache.files[file] = content;

            return cb(undefined, content);
        });
    }

    function compileHtml(mod, file, statusCode, debug, cb) {
        var cached = cache.templates[file];

        if (cached) {
            return cb(undefined, mod, cached, statusCode);
        }

        return locate(mod["public"], file, function (err, qfile) {
            if (err) {
                if (statusCode === HTTP_NOT_FOUND) {
                    return (cb)?cb(err):undefined;
                }

                return compileHtml(mod, "/404.html", cb, HTTP_NOT_FOUND, debug);
            }
            return load(cache, qfile, function (err, content) {
                if (err) {
                    return cb(err);
                }

                var template = hbars.compile(content);

                cache.templates[file] = template;

                return cb(undefined, mod, template, statusCode);
            }, debug);
        });
    }

    function compileSite(mod, debug, cb) {
        var htmlFiles = mod.htmlFiles,
            rawHtml = [],
            idx = 0,
            templateFiles = mod.templateFiles,
            templateHtml = [],
            file;

        function loadHtmlFiles(cb) {
            var pos = idx++;
            async.each(htmlFiles, function (file, callback) {
                load(cache, file, function (err, content) {
                    if (err) {
                        return callback(err);
                    }

                    rawHtml[pos] = content;
                    return callback();
                }, debug);
            }, cb);
        }

        function loadTemplateFiles(cb) {
            async.each(templateFiles, function (file, callback) {
                var name = path.basename(file, '.html');

                load(cache, file, function (err, content) {
                    if (err) {
                        return callback(err);
                    }

                    templateHtml.push({
                        name: name,
                        content: content
                    });
                    return callback();
                }, debug);
            }, cb);
        }

        if (!mod.compileSite) {
            async.parallel([
                loadHtmlFiles,
                loadTemplateFiles
            ], function (err) {
                if (err) {
                    debug.error(err);
                }
                mod.htmlFiles = rawHtml;
                mod.templateFiles = templateHtml;
                mod.compileSite = true;
                cb();
            });
        } else {
            cb();
        }
    }

    module.exports = {
        merge: merge,
        filter: filter,
        getCachedModule: getCachedModule,
        getModule: getModule,
        setModule: setModule,
        compileSite: compileSite,
        compileHtml: compileHtml,
        locateSync: locateSync
    };

})(module);<|endoftext|>


<commit_before>enterUrlMsg = "Drag-and-drop the video or paste its URL here...";

removeImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/x-4x.png";
pauseImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-pause-4x.png";
playImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-play-4x.png";

var videoUrl;
var videoName;
var videoTime = null;

var videos = [];
var videoCounter = 0;
var videoIteration = 0;
var videoPaused;
var timer;

function Timer(callback, delay) {
  var timerId, start, remaining = delay;

  this.pause = function() {
      window.clearTimeout(timerId);
      remaining -= new Date() - start;
  };

  this.resume = function() {
      start = new Date();
      window.clearTimeout(timerId);
      timerId = window.setTimeout(callback, remaining);
  };

  this.resume();
}

function msConversion(millis) {
  var minutes = Math.floor(millis / 60000);
  var seconds = ((millis % 60000) / 1000).toFixed(0);
  return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}

function highlight(i) {
  $("tr:nth-child(" + i + ")").attr("id", "newSelected");
  $("tr.selected").removeClass("selected");
  $("#newSelected").addClass("selected");
  $("#newSelected").removeAttr("id");
}

function addVideoToList(name, time) {
  $("#videosTable").append("
" + name + "<button class=\"removeButton\"></button>	" + time + "
");
}

function playVideo() {
  highlight(videoIteration);
  document.title = "Streamly - " + videos[videoIteration]["name"];

  var embedUrl = videos[videoIteration]["url"].replace("/watch?v=", "/embed/") + "?autoplay=1";
  $("#youtube").attr("src", embedUrl);
  $("#pauseImg").attr("src", pauseImgSrc);
}

function loopVideo() {
  videoIteration++;
  playVideo();
  timer = new Timer(function() {
    if (videoIteration < videoCounter) {
      loopVideo();
    }
    else {
      timer.pause();
      timer = 0;
      $("#youtube").attr("src", "");
      document.title = "Streamly";
    }
  }, videos[videoIteration]["time"] + 2000);
}

function pauseVideo() {
  if (!videoPaused) {
    timer.pause();
    $("#pauseImg").attr("src", playImgSrc);
    videoPaused = true;
  }
  else {
    timer.resume();
    $("#pauseImg").attr("src", pauseImgSrc);
    videoPaused = false;
  }
}

function backVideo() {
  if (videoIteration - 2 > -1) {
    videoIteration = videoIteration - 2;
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function forwardVideo() {
  if (videoIteration + 1 <= videoCounter) {
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function setPlaylist() {
  var playlist = JSON.stringify(videos);
  playlist = window.btoa(playlist);
  playlist = encodeURIComponent(playlist);
  window.location.hash = playlist;
}

function getPlaylist() {
  if (window.location.hash.substr(1) !== "") {
    var playlist = window.location.hash.substr(1);
    playlist = decodeURIComponent(playlist);
    playlist = window.atob(playlist);
    playlist = JSON.parse(playlist);
    videos = playlist;

    for (i = 1; i < videos.length; i++) {
      videoCounter = i;
      var printTime = msConversion(videos[videoCounter]["time"]);
      addVideoToList(videos[videoCounter]["name"], printTime);
    }
    loopVideo();
  }
}

function getVideoData() {
  $.ajax({
    url: videoUrl,
    type: 'GET',
    success: function(res) {
      var data = $(res.responseText);
      videoName = data.find("span#eow-title");
      videoName = videoName[0].textContent;
      videoName = videoName.trim();
      videoTime = null;
      for (iteration in data) {
        var str = data[iteration].innerHTML;
        if (videoTime == null && typeof str != "undefined") {
          videoTime = str.match(/,"length_seconds":"\d+",/g);
        }
      }
      videoTime = videoTime[0];
      videoTime = videoTime.replace(/,"length_seconds":"/g, "").replace(/",/g, "");
      videoTime = +videoTime * 1000;
    }
  });
}

function addVideo() {
  videoCounter++;
  var video = {};
  video["name"] = videoName;
  video["time"] = videoTime;
  video["url"] = videoUrl;
  videos[videoCounter] = video;

  var printTime = msConversion(videos[videoCounter]["time"]);

  addVideoToList(videoName, printTime);

  setPlaylist();

  if (videoCounter == 1 || timer == 0) {
    loopVideo();
  }
}

function removeVideo(which) {
  videoCounter--;
  videos.splice(which, 1);
  $("tr:nth-child("+ which +")").remove();
  setPlaylist();
}

function input() {
  switch ($("#inputBox").attr("placeholder")) {
    case enterUrlMsg:
      if ($("#inputBox").val() != "") {
        videoUrl = $("#inputBox").val();
        videoUrl = videoUrl.trim();
        $("#inputBox").val("").attr("placeholder", "Loading video data from YouTube...");
        getVideoData();
      }
      else {
        window.open("https://www.youtube.com");
      }
      break;
  }
}

$(document).ajaxStop(function() {
  $("#inputBox").val("").attr("placeholder", enterUrlMsg);
  addVideo();
});

function onDrop(event) {
  var data = event.dataTransfer.getData("URL");
  event.target.textContent = data;
  $("#inputBox").val(data);
  input();
  event.preventDefault();
}
<commit_msg>lets test it out<commit_after>enterUrlMsg = "Drag-and-drop the video or paste its URL here...";

removeImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/x-4x.png";
pauseImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-pause-4x.png";
playImgSrc = "//cdn.rawgit.com/iconic/open-iconic/master/png/media-play-4x.png";

var videoUrl;
var videoName;
var videoTime = null;

var videos = [];
var videoCounter = 0;
var videoIteration = 0;
var videoPaused;
var timer;

function Timer(callback, delay) {
  var timerId, start, remaining = delay;

  this.pause = function() {
      window.clearTimeout(timerId);
      remaining -= new Date() - start;
  };

  this.resume = function() {
      start = new Date();
      window.clearTimeout(timerId);
      timerId = window.setTimeout(callback, remaining);
  };

  this.resume();
}

function msConversion(millis) {
  var minutes = Math.floor(millis / 60000);
  var seconds = ((millis % 60000) / 1000).toFixed(0);
  return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}

function highlight(i) {
  $("tr:nth-child(" + i + ")").attr("id", "newSelected");
  $("tr.selected").removeClass("selected");
  $("#newSelected").addClass("selected");
  $("#newSelected").removeAttr("id");
}

function addVideoToList(name, time) {
  $("#videosTable").append("
" + name + "<button class=\"tableButton\" onclick=\"removeVideo(" + videoCounter + ");\"></button>	" + time + "
");
}

function playVideo() {
  highlight(videoIteration);
  document.title = "Streamly - " + videos[videoIteration]["name"];

  var embedUrl = videos[videoIteration]["url"].replace("/watch?v=", "/embed/") + "?autoplay=1";
  $("#youtube").attr("src", embedUrl);
  $("#pauseImg").attr("src", pauseImgSrc);
}

function loopVideo() {
  videoIteration++;
  playVideo();
  timer = new Timer(function() {
    if (videoIteration < videoCounter) {
      loopVideo();
    }
    else {
      timer.pause();
      timer = 0;
      $("#youtube").attr("src", "");
      document.title = "Streamly";
    }
  }, videos[videoIteration]["time"] + 2000);
}

function pauseVideo() {
  if (!videoPaused) {
    timer.pause();
    $("#pauseImg").attr("src", playImgSrc);
    videoPaused = true;
  }
  else {
    timer.resume();
    $("#pauseImg").attr("src", pauseImgSrc);
    videoPaused = false;
  }
}

function backVideo() {
  if (videoIteration - 2 > -1) {
    videoIteration = videoIteration - 2;
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function forwardVideo() {
  if (videoIteration + 1 <= videoCounter) {
    if (timer != 0) {
      timer.pause();
    }
    timer = 0;
    loopVideo();
  }
}

function setPlaylist() {
  var playlist = JSON.stringify(videos);
  playlist = window.btoa(playlist);
  playlist = encodeURIComponent(playlist);
  window.location.hash = playlist;
}

function getPlaylist() {
  if (window.location.hash.substr(1) !== "") {
    var playlist = window.location.hash.substr(1);
    playlist = decodeURIComponent(playlist);
    playlist = window.atob(playlist);
    playlist = JSON.parse(playlist);
    videos = playlist;

    for (i = 1; i < videos.length; i++) {
      videoCounter = i;
      var printTime = msConversion(videos[videoCounter]["time"]);
      addVideoToList(videos[videoCounter]["name"], printTime);
    }
    loopVideo();
  }
}

function getVideoData() {
  $.ajax({
    url: videoUrl,
    type: 'GET',
    success: function(res) {
      var data = $(res.responseText);
      videoName = data.find("span#eow-title");
      videoName = videoName[0].textContent;
      videoName = videoName.trim();
      videoTime = null;
      for (iteration in data) {
        var str = data[iteration].innerHTML;
        if (videoTime == null && typeof str != "undefined") {
          videoTime = str.match(/,"length_seconds":"\d+",/g);
        }
      }
      videoTime = videoTime[0];
      videoTime = videoTime.replace(/,"length_seconds":"/g, "").replace(/",/g, "");
      videoTime = +videoTime * 1000;
    }
  });
}

function addVideo() {
  videoCounter++;
  var video = {};
  video["name"] = videoName;
  video["time"] = videoTime;
  video["url"] = videoUrl;
  videos[videoCounter] = video;

  var printTime = msConversion(videos[videoCounter]["time"]);

  addVideoToList(videoName, printTime);

  setPlaylist();

  if (videoCounter == 1 || timer == 0) {
    loopVideo();
  }
}

function removeVideo(which) {
  videoCounter--;
  videos.splice(which, 1);
  $("tr:nth-child("+ which +")").remove();
  setPlaylist();
}

function input() {
  switch ($("#inputBox").attr("placeholder")) {
    case enterUrlMsg:
      if ($("#inputBox").val() != "") {
        videoUrl = $("#inputBox").val();
        videoUrl = videoUrl.trim();
        $("#inputBox").val("").attr("placeholder", "Loading video data from YouTube...");
        getVideoData();
      }
      else {
        window.open("https://www.youtube.com");
      }
      break;
  }
}

$(document).ajaxStop(function() {
  $("#inputBox").val("").attr("placeholder", enterUrlMsg);
  addVideo();
});

function onDrop(event) {
  var data = event.dataTransfer.getData("URL");
  event.target.textContent = data;
  $("#inputBox").val(data);
  input();
  event.preventDefault();
}
<|endoftext|>


<commit_before>var http = require("http"),
	https = require("https"),
	url = require("url");

module.exports = function(options, cb){
	if(typeof options === "string"){
		options = { uri: url.parse(options) };
	}
	return new Request(options, cb);
};

//Add methods for the most common cases
["GET", "POST", "HEAD", "DELETE", "UPDATE"].forEach(function(method){
	module.exports[method.toLowerCase()] = function(uri, cb){
		uri = url.parse(uri);
		uri.method = method;
		return new Request({ uri: uri }, cb);
	};
});

var protocols = {
	"http:": http,
	"https:": https
};
module.exports.addProtocol = function(name, module){
	protocols[name] = module;
};

var re_protocol = /^https?:/; //TODO: what about other protocols?

var Request = function(options, cb){
	this._options = options;
	this._cb = cb;

	this._ended = false;
	this._redirected = 0;
	this._resp = null;
	this.response = null;
	this._body = new Buffer(0);

	if(typeof options.uri === "string"){
		options.uri = url.parse(options.uri);
	} else if(typeof options.uri !== "object"){
		this.emit("error", Error("No URI specified!"));
		return;
	}

	//fix for node < 0.5
	if( !("path" in options.uri) ) options.uri.path = options.uri.pathname;

	this.writable = options.uri.method === "POST" || options.uri.method === "PUT";

	this._addListeners();

	if( !(options.uri.protocol in protocols) ){
		this.emit("error", Error("Unknown protocol: " + options.uri.protocol));
		return;
	}

	var scope = this;
	process.nextTick(function(){
		if(!scope._createRequest(options)) return;
		if(!scope.writable) scope._request.end();
		else{
			if(options.body) scope._request.write(options.body);
			scope._prepareClose();
		}
	});
};

var Stream = require("stream").Stream;
require("util").inherits(Request, Stream);

Request.prototype.readable = true;

//save the pipe function for later
var pipe = Stream.prototype.pipe;

Request.prototype.pipe = function(dest, opts){
	if(this._body){
		throw Error("Data was already emitted!");
	}
	else if(this._ended){
		throw Error("Request is closed!");
	}

	return pipe.call(this, dest, opts);
};

Request.prototype._prepareClose = function(){
	this._close = true;
	var scope = this;
	process.nextTick(function(){
		if(scope._close){
			scope._request.end();
			scope.writable = false;
		}
	});
};

Request.prototype._addListeners = function(){
	var scope = this;

	this.on("error", function(err){
		if(scope._cb) scope._cb(err);
		scope._cb = null; //remove the cb
	});

	this.on("end", function(){
		scope._ended = true;
	});

	if(this._cb){
		this.on("data", function(chunk){
			scope._body = Buffer.concat([scope._body, chunk]);
		});
		this.on("end", function(){
			scope._cb(null, scope.response, scope._body);
		});
	}

	this.on("redirect", function(location){
		var options = scope._options,
			method = options.uri.method;

		if(scope._redirected++ < (options.maxRedirects || 10)){
			if(!re_protocol.test(location)){
				location = url.resolve(options.uri, location);
			}

			options.uri = url.parse(location);
			options.uri.method = method;

			scope._createRequest(options);
			scope._request.end();
		} else {
			scope.emit("error", Error("Too many redirects"));
		 }
	});

	this.once("pipe", function(src){
		if(!scope.writable){
			scope.emit("error", Error("Can't write to socket!"));
			return;
		}

		 scope._close = false;
		 var cb = function(){
			scope._prepareClose();
		 };
		 src.on("end", cb);
		 src.on("close", cb);

		 scope.on("pipe", function(){
			throw Error("There is already a pipe");
		 });
	});
};

Request.prototype._createRequest = function(options){
	var req = protocols[options.uri.protocol],
	    scope = this;

	this._request = req.request(options.uri, function(resp){
		var statusCode = resp.statusCode;

		if( (!("followRedirect" in options) || options.followRedirect) && (statusCode % 300 < 99) && !scope.writable && resp.headers.location){
				clearTimeout(scope._reqTimeout);
				scope.abort(); //close the socket
				scope.emit("redirect", resp.headers.location);
				return;
		}

		if(options.only2xx && statusCode % 200 >= 100){
			scope.emit("error", Error("Received status code " + statusCode + " (\"" + http.STATUS_CODES[statusCode] + "\")"));
			scope.abort();
		}

		//add some info to the scope
		scope.response = {
			location: url.format(options.uri),
			statusCode: statusCode,
			headers: resp.headers
		};

		scope._resp = resp;

		if(options.encoding){
			resp.setEncoding(options.encoding);
		}

		resp.on("end", function(){
			if(!scope._ended) scope.emit("end");
		}).on("close", function(){
			scope.emit("close");
		}).on("error", function(err){
			scope.emit("error", err);
		}).on("data", function(chunk){
			scope.emit("data", chunk);
		});

		scope.emit("response", resp);
	});

	this._request.on("error", function(err){
		scope.emit("error", err);
	});

	if(this._options.headers){
		for(var header in this._options.headers){
			this._request.setHeader(header, this._options.headers[header]);
		}
	}

	if(!("timeout" in options) || options.timeout){
		this._reqTimeout = setTimeout(function(){
			if(!scope._ended){
				scope._request.abort();

				scope.emit("timeout");
				scope.emit("error", Error("ETIMEDOUT"));
			}
		}, options.timeout || 1e4);
	}

	return true;
};

Request.prototype.setEncoding = function(encoding){
	//if we are connected, send the encoding to the response
	if(this._resp) this._resp.setEncoding(encoding);
	//else, safe it for later
	else this._options.encoding = encoding;
};
Request.prototype.abort = function(){
	this._request.abort();
};
Request.prototype.write = function(chunk){
	if(!this.writable) throw Error("Either request method doesn't support .write or request was sent!");
	return this._request.write(chunk);
};
Request.prototype.setHeader = function(name, value){
	if(arguments.length < 2) throw Error("wrong number of arguments");
	if(this._request){
		return this._request.setHeader(name, value);
	}
	if(typeof this._options.headers !== "object"){
		this._options.headers = {__proto__: null};
	}
	this._options.headers[name.toLowerCase()] = value;
};
Request.prototype.getHeader = function(name){
	if(arguments.length < 1) throw Error("wrong number of arguments");
	if(this._request){
		return this._request.getHeader(name);
	}
	return this._options.headers && this._options.headers[name.toLowerCase()];
};
Request.prototype.removeHeader = function(name){
	if(arguments.length < 1) throw Error("wrong number of arguments");
	if(this._request){
		return this._request.removeHeader(name);
	}
	return this._options.headers && delete this._options.headers[name.toLowerCase()];
};

module.exports.Request = Request;<commit_msg>don't permit 5xx status codes for only2xx option<commit_after>var http = require("http"),
	https = require("https"),
	url = require("url");

module.exports = function(options, cb){
	if(typeof options === "string"){
		options = { uri: url.parse(options) };
	}
	return new Request(options, cb);
};

//Add methods for the most common cases
["GET", "POST", "HEAD", "DELETE", "UPDATE"].forEach(function(method){
	module.exports[method.toLowerCase()] = function(uri, cb){
		uri = url.parse(uri);
		uri.method = method;
		return new Request({ uri: uri }, cb);
	};
});

var protocols = {
	"http:": http,
	"https:": https
};
module.exports.addProtocol = function(name, module){
	protocols[name] = module;
};

var re_protocol = /^https?:/; //TODO: what about other protocols?

var Request = function(options, cb){
	this._options = options;
	this._cb = cb;

	this._ended = false;
	this._redirected = 0;
	this._resp = null;
	this.response = null;
	this._body = new Buffer(0);

	if(typeof options.uri === "string"){
		options.uri = url.parse(options.uri);
	} else if(typeof options.uri !== "object"){
		this.emit("error", Error("No URI specified!"));
		return;
	}

	//fix for node < 0.5
	if( !("path" in options.uri) ) options.uri.path = options.uri.pathname;

	this.writable = options.uri.method === "POST" || options.uri.method === "PUT";

	this._addListeners();

	if( !(options.uri.protocol in protocols) ){
		this.emit("error", Error("Unknown protocol: " + options.uri.protocol));
		return;
	}

	var scope = this;
	process.nextTick(function(){
		if(!scope._createRequest(options)) return;
		if(!scope.writable) scope._request.end();
		else{
			if(options.body) scope._request.write(options.body);
			scope._prepareClose();
		}
	});
};

var Stream = require("stream").Stream;
require("util").inherits(Request, Stream);

Request.prototype.readable = true;

//save the pipe function for later
var pipe = Stream.prototype.pipe;

Request.prototype.pipe = function(dest, opts){
	if(this._body){
		throw Error("Data was already emitted!");
	}
	else if(this._ended){
		throw Error("Request is closed!");
	}

	return pipe.call(this, dest, opts);
};

Request.prototype._prepareClose = function(){
	this._close = true;
	var scope = this;
	process.nextTick(function(){
		if(scope._close){
			scope._request.end();
			scope.writable = false;
		}
	});
};

Request.prototype._addListeners = function(){
	var scope = this;

	this.on("error", function(err){
		if(scope._cb) scope._cb(err);
		scope._cb = null; //remove the cb
	});

	this.on("end", function(){
		scope._ended = true;
	});

	if(this._cb){
		this.on("data", function(chunk){
			scope._body = Buffer.concat([scope._body, chunk]);
		});
		this.on("end", function(){
			scope._cb(null, scope.response, scope._body);
		});
	}

	this.on("redirect", function(location){
		var options = scope._options,
			method = options.uri.method;

		if(scope._redirected++ < (options.maxRedirects || 10)){
			if(!re_protocol.test(location)){
				location = url.resolve(options.uri, location);
			}

			options.uri = url.parse(location);
			options.uri.method = method;

			scope._createRequest(options);
			scope._request.end();
		} else {
			scope.emit("error", Error("Too many redirects"));
		 }
	});

	this.once("pipe", function(src){
		if(!scope.writable){
			scope.emit("error", Error("Can't write to socket!"));
			return;
		}

		 scope._close = false;
		 var cb = function(){
			scope._prepareClose();
		 };
		 src.on("end", cb);
		 src.on("close", cb);

		 scope.on("pipe", function(){
			throw Error("There is already a pipe");
		 });
	});
};

Request.prototype._createRequest = function(options){
	var req = protocols[options.uri.protocol],
	    scope = this;

	this._request = req.request(options.uri, function(resp){
		var statusCode = resp.statusCode;

		if( (!("followRedirect" in options) || options.followRedirect) && (statusCode % 300 < 99) && !scope.writable && resp.headers.location){
				clearTimeout(scope._reqTimeout);
				scope.abort(); //close the socket
				scope.emit("redirect", resp.headers.location);
				return;
		}

		if(options.only2xx && (statusCode < 200 || statusCode >= 300)){
			scope.emit("error", Error("Received status code " + statusCode + " (\"" + http.STATUS_CODES[statusCode] + "\")"));
			scope.abort();
		}

		//add some info to the scope
		scope.response = {
			location: url.format(options.uri),
			statusCode: statusCode,
			headers: resp.headers
		};

		scope._resp = resp;

		if(options.encoding){
			resp.setEncoding(options.encoding);
		}

		resp.on("end", function(){
			if(!scope._ended) scope.emit("end");
		}).on("close", function(){
			scope.emit("close");
		}).on("error", function(err){
			scope.emit("error", err);
		}).on("data", function(chunk){
			scope.emit("data", chunk);
		});

		scope.emit("response", resp);
	});

	this._request.on("error", function(err){
		scope.emit("error", err);
	});

	if(this._options.headers){
		for(var header in this._options.headers){
			this._request.setHeader(header, this._options.headers[header]);
		}
	}

	if(!("timeout" in options) || options.timeout){
		this._reqTimeout = setTimeout(function(){
			if(!scope._ended){
				scope._request.abort();

				scope.emit("timeout");
				scope.emit("error", Error("ETIMEDOUT"));
			}
		}, options.timeout || 1e4);
	}

	return true;
};

Request.prototype.setEncoding = function(encoding){
	//if we are connected, send the encoding to the response
	if(this._resp) this._resp.setEncoding(encoding);
	//else, safe it for later
	else this._options.encoding = encoding;
};
Request.prototype.abort = function(){
	this._request.abort();
};
Request.prototype.write = function(chunk){
	if(!this.writable) throw Error("Either request method doesn't support .write or request was sent!");
	return this._request.write(chunk);
};
Request.prototype.setHeader = function(name, value){
	if(arguments.length < 2) throw Error("wrong number of arguments");
	if(this._request){
		return this._request.setHeader(name, value);
	}
	if(typeof this._options.headers !== "object"){
		this._options.headers = {__proto__: null};
	}
	this._options.headers[name.toLowerCase()] = value;
};
Request.prototype.getHeader = function(name){
	if(arguments.length < 1) throw Error("wrong number of arguments");
	if(this._request){
		return this._request.getHeader(name);
	}
	return this._options.headers && this._options.headers[name.toLowerCase()];
};
Request.prototype.removeHeader = function(name){
	if(arguments.length < 1) throw Error("wrong number of arguments");
	if(this._request){
		return this._request.removeHeader(name);
	}
	return this._options.headers && delete this._options.headers[name.toLowerCase()];
};

module.exports.Request = Request;<|endoftext|>


<commit_before>const udp          = require('dgram');
const util         = require('util');
const EventEmitter = require('events');

/**
 * [Discovery description]
 */
function Discovery(options){
  if(!(this instanceof Discovery)){
    return new Discovery(options);
  }
  EventEmitter.call(this);
  var defaults = {
    port     : 1982,
    multicast: '239.255.255.250'
  };
  for(var k in options){
    defaults[ k ] = options[ k ];
  }
  this.options = defaults;
  this.socket = udp.createSocket('udp4');
  this.socket.on('message', this.process.bind(this));
  return this;
};

util.inherits(Discovery, EventEmitter);

Discovery.prototype.process = function(message, rinfo){
  console.log(message, rinfo);
  return this;
};

/**
 * [escape description]
 * @param  {[type]} str [description]
 * @return {[type]}     [description]
 */
Discovery.escape = function(str){
  if(/[:|\s]/.test(str))
    return '"' + str + '"';
  return str;
};
/**
 * [search description]
 * @param  {[type]} path    [description]
 * @param  {[type]} headers [description]
 * @return {[type]}         [description]
 */
Discovery.prototype.search = function(path, headers){
  var method = 'M-SEARCH';
  path = path || '*';
  headers = headers || {};
  return this.send(method, path, headers);
}
/**
 * [send description]
 * @param  {[type]} method  [description]
 * @param  {[type]} path    [description]
 * @param  {[type]} headers [description]
 * @return {[type]}         [description]
 */
Discovery.prototype.send = function(method, path, headers){
  var request = [
    [ method, path, 'HTTP/1.1' ].join(' ')
  ];
  Object.keys(headers).forEach(name => {
    request.push([ name, Discovery.escape(headers[ name ]) ].join(': '));
  });
  var message = request.join('\r\n');
  console.log(message);
  this.socket.send(message, 0, message.length,
    this.options.port, this.options.multicast, function(){});
  return this;
};
/**
 * [listen description]
 * @param  {[type]}   port     [description]
 * @param  {Function} callback [description]
 * @return {[type]}            [description]
 */
Discovery.prototype.listen = function(port, callback){
  var err = null;
  try{
    this.socket.bind(port || this.options.port);
  }catch(e){
    err = e;
  }
  callback && callback(err);
  return this;
};

module.exports = Discovery;<commit_msg>fix bug<commit_after>const udp          = require('dgram');
const util         = require('util');
const EventEmitter = require('events');

/**
 * [Discovery description]
 */
function Discovery(options){
  if(!(this instanceof Discovery)){
    return new Discovery(options);
  }
  EventEmitter.call(this);
  var defaults = {
    port     : 1982,
    multicast: '239.255.255.250'
  };
  for(var k in options){
    defaults[ k ] = options[ k ];
  }
  this.options = defaults;
  this.socket = udp.createSocket('udp4');
  this.socket.on('message', this.process.bind(this));
  return this;
};

util.inherits(Discovery, EventEmitter);

Discovery.prototype.process = function(message, rinfo){
  console.log(message, rinfo);
  return this;
};

/**
 * [escape description]
 * @param  {[type]} str [description]
 * @return {[type]}     [description]
 */
Discovery.escape = function(str){
  if(/[:|\s]/.test(str))
    return '"' + str + '"';
  return str;
};
/**
 * [search description]
 * @param  {[type]} path    [description]
 * @param  {[type]} headers [description]
 * @return {[type]}         [description]
 */
Discovery.prototype.search = function(path, headers){
  var method = 'M-SEARCH';
  path = path || '*';
  headers = headers || {};
  return this.send(method, path, headers);
}
/**
 * [send description]
 * @param  {[type]} method  [description]
 * @param  {[type]} path    [description]
 * @param  {[type]} headers [description]
 * @return {[type]}         [description]
 */
Discovery.prototype.send = function(method, path, headers){
  var request = [
    [ method, path, 'HTTP/1.1' ].join(' ')
  ];
  Object.keys(headers).forEach(name => {
    request.push([ name, Discovery.escape(headers[ name ]) ].join(': '));
  });
  var message = request.join('\r\n');
  console.log(message);
  this.socket.send(message, 0, message.length,
    this.options.port, this.options.multicast, function(){});
  return this;
};
/**
 * [listen description]
 * @param  {[type]}   port     [description]
 * @param  {Function} callback [description]
 * @return {[type]}            [description]
 */
Discovery.prototype.listen = function(port, callback){
  if(typeof port === 'function'){
    callback = port; port = null;
  }
  this.socket.bind(port || this.options.port, function(err){
    this.setBroadcast(true);
    callback && callback(err);
  });
  return this;
};

module.exports = Discovery;<|endoftext|>


<commit_before>/*!
 * micromatch <https://github.com/jonschlinkert/micromatch>
 *
 * Copyright (c) 2014 Jon Schlinkert, contributors.
 * Licensed under the MIT license.
 */

'use strict';

var unixify = require('unixify');
var union = require('arr-union');
var diff = require('arr-diff');
var braces = require('braces');

function makeRe(glob, options) {
  var opts = options || {};
  var flags = '';
  var i = 0;

  // only recompile regex if options change
  optsCache = optsCache || opts;
  if (!equal(optsCache, opts)) {
    regex = null;
    cache = glob;
  }

  // only recompile regex if glob changes
  cache = cache || glob;
  if (cache !== glob) {
    glob = unixify(glob);
    regex = null;
    cache = glob;
  }

  // if `true`, then we can just return
  // the regex that was previously cached
  if (regex instanceof RegExp) {
    return regex;
  }

  // expand `{1..5}` braces
  if (/\{/.test(glob)) {
    glob = expand(glob);
  }

  var len = tokens.length;
  while (i < len) {
    var group = tokens[i++];
    var re = group[1].re;
    var to = group[1].to;

    // apply special cases from options
    for (var key in opts) {
      if (group[1].hasOwnProperty(key)) {
        to += group[1][key];
      }
    }
    glob = glob.replace(re, to);
  }

  if (opts.nocase) flags += 'i';

  // cache the regex
  regex = globRegex(glob, flags);
  // return the result
  return regex;
}

/**
 * Pass an array of files and a glob pattern as a string.
 * This function is called by the main `micromatch` function
 * If you only need to pass a single pattern you might get
 * minor speed improvements using this function.
 *
 * @param  {Array} `files`
 * @param  {Array} `pattern`
 * @param  {Object} `options`
 * @return {Array}
 */

function match(files, pattern, options) {
  if (typeof files !== 'string' && !Array.isArray(files)) {
    throw new Error('micromatch.match() expects a string or array.');
  }

  files = arrayify(files);
  var len = files.length;
  var res = [];
  var i = 0;

  while (i < len) {
    var fp = unixify(files[i++]);
    if (makeRe(pattern, options).test(fp)) {
      res.push(fp);
    }
  }
  return res;
}

/**
 * The main function. Pass an array of filepaths,
 * and a string or array of glob patterns
 *
 * @param  {Array|String} `files`
 * @param  {Array|String} `patterns`
 * @param  {Object} `opts`
 * @return {Array} Array of matches
 */

function micromatch(files, patterns, opts) {
  if (!files || !patterns) {
    return [];
  }

  files = arrayify(files);
  opts = opts || {};

  if (typeof patterns === 'string') {
    return union([], match(files, patterns, opts));
  }

  var len = patterns.length;
  var res = [];
  var i = 0;

  while (len--) {
    var glob = patterns[i++];
    var concat = union;

    if (glob.charAt(0) === '!') {
      glob = glob.slice(1);
      concat = diff;
    }

    res = concat(res, match(files, glob, opts));
  }
  return res;
}

/**
 * Create the regex to do the matching. If
 * the leading character in the `glob` is `!`
 * a negation regex is returned.
 *
 * @param {String} `glob`
 * @param {String} `flags`
 */

function globRegex(glob, flags) {
  var  res = '^' + glob + '$';
  if (/^!/.test(glob)) {
    res = '^(?!((?!\\.)' + glob.slice(1) + ')$)';
  }
  return new RegExp(res, flags);
}

/**
 * Regex for matching single-level braces
 */

function bracesRegex() {
  return /\{([^{]+)\}/g;
}

/**
 * Expand braces in the given glob pattern.
 *
 * @param  {String} `glob`
 * @return {String}
 */

function expand (glob) {
  if (!isBasicBrace(glob)) {
    // avoid sending the glob to the `braces` lib if not necessary
    return glob.replace(bracesRegex(), function (_, inner) {
      return '(' + inner.split(',').join('|') + ')';
    });
  } else {
    // if it's nested, we'll use `braces`
    return braces(glob).join('|');
  }
}

/**
 * Return `true` if the path contains nested
 * braces. If so, then the [braces] lib is used
 * for expansion. if not, we convert the braces
 * to a regex.
 *
 * @param  {String} str
 * @return {Boolean}
 */

function isBasicBrace(str) {
  if (/\.{2}/.test(str)) {
    return true;
  }

  var a = str.indexOf('{');
  str = str.slice(a + 1);
  var i = 0;

  while (a !== -1) {
    var ch = str.charAt(i++);
    if (ch === '{') {
      return true;
    }
    if (ch === '}') {
      return false;
    }
  }
  return false;
}

/**
 * Return true if object A is equal (enough)
 * to object B. Used for options caching.
 *
 * @param {Object} a
 * @param {Object} b
 * @return {Boolean}
 */

function equal(a, b) {
  if (!b) return false;

  for (var prop in b) {
    if (!a.hasOwnProperty(prop)) {
      return false;
    }
    if (a[prop] !== b[prop]) {
      return false;
    }
  }
  return true;
}

/**
 * Coerce `val` to an array
 *
 * @param  {*} val
 * @return {Array}
 */

function arrayify(val) {
  return !Array.isArray(val)
    ? [val]
    : val;
}

/**
 * Results cache
 */

var regex;
var cache;
var optsCache;

/**
 * Special patterns
 */

var matchBase = '[\\s\\S]+';
var dotfile =   '[^\\/]*?';

/**
 * Glob tokens to match and replace with
 * regular expressions
 */

var tokens = [
  ['\\\\', {re: /\\{2}/g,   to: '\\/'}],
  ['/',    {re: /\//g,      to: '\\/'}],
  ['.',    {re: /[.]/g,     to: '\\.'}],
  ['?',    {re: /\?/g,      to: '.'}],
  ['**',   {re: /[*]{2}/g,  to: '[\\s\\S]+'}],
  ['*',    {re: /[*]/g,     to: '[^\\/]*?', matchBase: matchBase, dot: dotfile}],
];

/**
 * Expose `micromatch`
 */

module.exports = micromatch;

/**
 * Expose `micromatch.match`
 */

module.exports.match = match;

/**
 * Expose `micromatch.makeRe`
 */

module.exports.makeRe = makeRe;
<commit_msg>expose regex `flags` on the options<commit_after>/*!
 * micromatch <https://github.com/jonschlinkert/micromatch>
 *
 * Copyright (c) 2014 Jon Schlinkert, contributors.
 * Licensed under the MIT license.
 */

'use strict';

var unixify = require('unixify');
var union = require('arr-union');
var diff = require('arr-diff');
var braces = require('braces');

function makeRe(glob, options) {
  var opts = options || {};
  var flags = opts.flags || '';
  var i = 0;

  // only recompile regex if options change
  optsCache = optsCache || opts;
  if (!equal(optsCache, opts)) {
    regex = null;
    cache = glob;
  }

  // only recompile regex if glob changes
  cache = cache || glob;
  if (cache !== glob) {
    glob = unixify(glob);
    regex = null;
    cache = glob;
  }

  // if `true`, then we can just return
  // the regex that was previously cached
  if (regex instanceof RegExp) {
    return regex;
  }

  // expand `{1..5}` braces
  if (/\{/.test(glob)) {
    glob = expand(glob);
  }

  var len = tokens.length;
  while (i < len) {
    var group = tokens[i++];
    var re = group[1].re;
    var to = group[1].to;

    // apply special cases from options
    for (var key in opts) {
      if (group[1].hasOwnProperty(key)) {
        to += group[1][key];
      }
    }
    glob = glob.replace(re, to);
  }

  if (opts.nocase) flags += 'i';

  // cache the regex
  regex = globRegex(glob, flags);
  // return the result
  return regex;
}

/**
 * Pass an array of files and a glob pattern as a string.
 * This function is called by the main `micromatch` function
 * If you only need to pass a single pattern you might get
 * minor speed improvements using this function.
 *
 * @param  {Array} `files`
 * @param  {Array} `pattern`
 * @param  {Object} `options`
 * @return {Array}
 */

function match(files, pattern, options) {
  if (typeof files !== 'string' && !Array.isArray(files)) {
    throw new Error('micromatch.match() expects a string or array.');
  }

  files = arrayify(files);
  var len = files.length;
  var res = [];
  var i = 0;

  while (i < len) {
    var fp = unixify(files[i++]);
    if (makeRe(pattern, options).test(fp)) {
      res.push(fp);
    }
  }
  return res;
}

/**
 * The main function. Pass an array of filepaths,
 * and a string or array of glob patterns
 *
 * @param  {Array|String} `files`
 * @param  {Array|String} `patterns`
 * @param  {Object} `opts`
 * @return {Array} Array of matches
 */

function micromatch(files, patterns, opts) {
  if (!files || !patterns) {
    return [];
  }

  files = arrayify(files);
  opts = opts || {};

  if (typeof patterns === 'string') {
    return union([], match(files, patterns, opts));
  }

  var len = patterns.length;
  var res = [];
  var i = 0;

  while (len--) {
    var glob = patterns[i++];
    var concat = union;

    if (glob.charAt(0) === '!') {
      glob = glob.slice(1);
      concat = diff;
    }

    res = concat(res, match(files, glob, opts));
  }
  return res;
}

/**
 * Create the regex to do the matching. If
 * the leading character in the `glob` is `!`
 * a negation regex is returned.
 *
 * @param {String} `glob`
 * @param {String} `flags`
 */

function globRegex(glob, flags) {
  var  res = '^' + glob + '$';
  if (/^!/.test(glob)) {
    res = '^(?!((?!\\.)' + glob.slice(1) + ')$)';
  }
  return new RegExp(res, flags);
}

/**
 * Regex for matching single-level braces
 */

function bracesRegex() {
  return /\{([^{]+)\}/g;
}

/**
 * Expand braces in the given glob pattern.
 *
 * @param  {String} `glob`
 * @return {String}
 */

function expand (glob) {
  if (!isBasicBrace(glob)) {
    // avoid sending the glob to the `braces` lib if not necessary
    return glob.replace(bracesRegex(), function (_, inner) {
      return '(' + inner.split(',').join('|') + ')';
    });
  } else {
    // if it's nested, we'll use `braces`
    return braces(glob).join('|');
  }
}

/**
 * Return `true` if the path contains nested
 * braces. If so, then the [braces] lib is used
 * for expansion. if not, we convert the braces
 * to a regex.
 *
 * @param  {String} str
 * @return {Boolean}
 */

function isBasicBrace(str) {
  if (/\.{2}/.test(str)) {
    return true;
  }

  var a = str.indexOf('{');
  str = str.slice(a + 1);
  var i = 0;

  while (a !== -1) {
    var ch = str.charAt(i++);
    if (ch === '{') {
      return true;
    }
    if (ch === '}') {
      return false;
    }
  }
  return false;
}

/**
 * Return true if object A is equal (enough)
 * to object B. Used for options caching.
 *
 * @param {Object} a
 * @param {Object} b
 * @return {Boolean}
 */

function equal(a, b) {
  if (!b) return false;

  for (var prop in b) {
    if (!a.hasOwnProperty(prop)) {
      return false;
    }
    if (a[prop] !== b[prop]) {
      return false;
    }
  }
  return true;
}

/**
 * Coerce `val` to an array
 *
 * @param  {*} val
 * @return {Array}
 */

function arrayify(val) {
  return !Array.isArray(val)
    ? [val]
    : val;
}

/**
 * Results cache
 */

var regex;
var cache;
var optsCache;

/**
 * Special patterns
 */

var matchBase = '[\\s\\S]+';
var dotfile =   '[^\\/]*?';

/**
 * Glob tokens to match and replace with
 * regular expressions
 */

var tokens = [
  ['\\\\', {re: /\\{2}/g,   to: '\\/'}],
  ['/',    {re: /\//g,      to: '\\/'}],
  ['.',    {re: /[.]/g,     to: '\\.'}],
  ['?',    {re: /\?/g,      to: '.'}],
  ['**',   {re: /[*]{2}/g,  to: '[\\s\\S]+'}],
  ['*',    {re: /[*]/g,     to: '[^\\/]*?', matchBase: matchBase, dot: dotfile}],
];

/**
 * Expose `micromatch`
 */

module.exports = micromatch;

/**
 * Expose `micromatch.match`
 */

module.exports.match = match;

/**
 * Expose `micromatch.makeRe`
 */

module.exports.makeRe = makeRe;
<|endoftext|>


<commit_before>'use strict'

var nextafter = require('math-float64-nextafter');

function assert(v) {
  if (!v) {
    throw new Error('Assertion error');
  }
}

var INITIAL_LEVEL = 0; // It doesn't seem useful for this to be a setting

function EnvGen(audioContext, targetParam) {
  // Support instantiating w/o new
  if (!(this instanceof EnvGen)) {
    return new EnvGen(audioContext, targetParam);
  }

  this._audioContext = audioContext;
  this._targetParam = targetParam;

  var _this = this;

  Object.defineProperty(this, 'mode', {
    get: function() { return _this._mode; },
    set: function(value) {
      if (_this.MODES.indexOf(value) >= 0) {
        // If we're currently in a 'sustain' state, and we switched into AD mode,
        // we would get stuck in sustain state. So just to be safe, whenever mode
        // is changed we fake a gate-off signal.
        _this.gate(false, nextafter(Math.max(this._lastGateTime, audioContext.currentTime), Infinity));

        _this._mode = value;
      }
    }
  });

  Object.defineProperty(this, 'attackShape', {
    get: function() { return _this._attackShape; },
    set: function(value) {
      if (_this.ATTACK_SHAPES.indexOf(value) >= 0) {
        _this._attackShape = value;
      }
    }
  });

  Object.defineProperty(this, 'attackRate', {
    get: function() { return _this._attackRate; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value > 0)) {
        _this._attackRate = value;
      }
    }
  });

  Object.defineProperty(this, 'attackLevel', {
    get: function() { return _this._attackLevel; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value)) {
        _this._attackLevel = value;
      }
    }
  });

  Object.defineProperty(this, 'decayShape', {
    get: function() { return _this._decayShape; },
    set: function(value) {
      if (_this.DECAY_SHAPES.indexOf(value) >= 0) {
        _this._decayShape = value;
      }
    }
  });

  Object.defineProperty(this, 'decayRate', {
    get: function() { return _this._decayRate; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value > 0)) {
        _this._decayRate = value;
      }
    }
  });

  Object.defineProperty(this, 'sustainFraction', {
    get: function() { return _this._sustainFraction; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value >= 0) && (value <= 1)) {
        _this._sustainFraction = value;
      }
    }
  });

  Object.defineProperty(this, 'releaseShape', {
    get: function() { return _this._releaseShape; },
    set: function(value) {
      if (_this.RELEASE_SHAPES.indexOf(value) >= 0) {
        _this._releaseShape = value;
      }
    }
  });

  Object.defineProperty(this, 'releaseRate', {
    get: function() { return _this._releaseRate; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value > 0)) {
        _this._releaseRate = value;
      }
    }
  });

  // Default settings
  this._mode = 'ADSR';
  this._attackShape = this.LINEAR;
  this._attackRate = 2;
  this._attackLevel = 1.0;
  this._decayShape = this.EXPONENTIAL;
  this._decayRate = 1;
  this._sustainFraction = 0.5;
  this._releaseShape = this.EXPONENTIAL;
  this._releaseRate = 1;

  this._targetParam.value = INITIAL_LEVEL;

  // Each segment has properties:
  //  beginTime: inclusive
  //  endTime: exclusive. may be +inf
  //  beginValue
  //  endValue
  //  shape: this.LINEAR, etc.
  //  rate: abs(slope-of-value) for LINEAR, abs(slope-of-log(value)) for EXPONENTIAL shapes
  this._scheduledSegments = [];

  // Track info about last gate we received
  this._lastGateTime = audioContext.currentTime;
  this._lastGateState = false;
}

EnvGen.prototype.MODES = ['AD', 'ASR', 'ADSR'];

EnvGen.prototype.LINEAR = 'L';
EnvGen.prototype.EXPONENTIAL = 'E';

EnvGen.prototype.ATTACK_SHAPES = [EnvGen.prototype.LINEAR];
EnvGen.prototype.DECAY_SHAPES = [EnvGen.prototype.LINEAR, EnvGen.prototype.EXPONENTIAL];
EnvGen.prototype.RELEASE_SHAPES = [EnvGen.prototype.LINEAR, EnvGen.prototype.EXPONENTIAL];

EnvGen.prototype._computeScheduledValue = function(time) {
  if (!this._scheduledSegments.length) {
    // If there are no scheduled segments, that means envelope should be at zero
    return 0;
  }

  // Sanity check: Scheduled segments should not start in the future
  assert(time >= this._scheduledSegments[0].beginTime);

  // Find what scheduled segment (if any) would be active at given time
  var activeIdx; // index into _scheduledSegments of one that is active at given time, if any
  for (var i = 0; i < this._scheduledSegments.length; i++) {
    var seg = this._scheduledSegments[i];
    if ((time >= seg.beginTime) && (time < seg.endTime)) {
      activeIdx = i;
      break;
    }
  }

  if (activeIdx === undefined) {
    // This must mean that time is after last scheduled segment
    var lastSeg = this._scheduledSegments[this._scheduledSegments.length-1];
    assert(time >= lastSeg.endTime); // sanity check
    return lastSeg.endValue;
  }

  // If we got this far, then the given time falls within a scheduled segment
  var activeSeg = this._scheduledSegments[activeIdx];

  // Determine the mid-segment value at the given time
  if (activeSeg.shape === this.LINEAR) {
    if (activeSeg.beginValue === activeSeg.endValue) {
      // Special case this since endTime may be +inf
      return activeSeg.beginValue;
    }

    // LERP
    // TODO: should be equal to activeSeg.beginValue + activeSeg.rate*(time - activeSeg.beginTime)
    return activeSeg.beginValue + ((time - activeSeg.beginTime)/(activeSeg.endTime - activeSeg.beginTime))*(activeSeg.endValue - activeSeg.beginValue);
  } else if (activeSeg.shape === this.EXPONENTIAL) {
    return activeSeg.endValue + (activeSeg.beginValue - activeSeg.endValue)*Math.exp(activeSeg.rate*(activeSeg.beginTime - time));
  } else {
    assert(false);
  }
};

EnvGen.prototype._scheduleSegment = function(endValue, shape, rate) {
  assert(this._scheduledSegments.length > 0);
  var lastSeg = this._scheduledSegments[this._scheduledSegments.length - 1];

  // It doesn't make sense to schedule a segment after an infinite one
  assert(lastSeg.endTime !== Infinity);

  if (lastSeg.endValue === endValue) {
    // We can just skip this segment since value is already there
    return;
  }

  // Based on shape, compute end time and call appropriate scheduling method
  var endTime;
  if (rate === Infinity) {
    // Special case this
    endTime = lastSeg.endTime;
    this._targetParam.setValueAtTime(endValue, endTime); // Spec says this should work. "If one of these events is added at a time where there is already one or more events, then it will be placed in the list after them, but before events whose times are after the event."
  } else {
    switch (shape) {
      case this.LINEAR:
        endTime = lastSeg.endTime + Math.abs((endValue - lastSeg.endValue)/rate);
        this._targetParam.linearRampToValueAtTime(endValue, endTime);
        break;

      case this.EXPONENTIAL:
        endTime = Infinity;
        this._targetParam.setTargetAtTime(endValue, lastSeg.endTime, 1.0/rate);
        break;

      default:
        assert(false);
    }
  }

  this._scheduledSegments.push({
    shape: shape,
    beginTime: lastSeg.endTime,
    endTime: endTime,
    beginValue: lastSeg.endValue,
    endValue: endValue,
    rate: rate,
  });
};

EnvGen.prototype.gate = function(on, time) {
  // Note the current AudioContext time
  var ct = this._audioContext.currentTime;

  // Default time parameter to current time
  time = (time === undefined) ? ct : time;

  if (time < this._lastGateTime) {
    // Gates can only have times >= the times of previously supplied gates.
    // If we receive a bad one, log a warning and ignore
    console.warn('Received gate with time earlier than a previous gate');
    return;
  }

  this._lastGateTime = time;
  this._lastGateState = on;

  // Special case: In AD mode, we ignore gate-off
  if ((this._mode === 'AD') && !on) {
    return;
  }

  // Determine value that we'll start from
  var startValue = this._computeScheduledValue(time);

  // Set anchor point at given value
  this._targetParam.setValueAtTime(startValue, time);

  // Cancel all scheduled changes after that
  this._targetParam.cancelScheduledValues(nextafter(time, Infinity));

  // Reinit scheduled segments array with a 'dummy' segment to simplify code
  this._scheduledSegments = [{
    beginTime: time,
    endTime: time,
    beginValue: startValue,
    endValue: startValue,
    shape: this.LINEAR,
  }];

  if (on) {
    // Schedule attack
    this._scheduleSegment(this._attackLevel, this._attackShape, this._attackRate);

    // Schedule decay, if needed
    if ((this._mode === 'AD') || (this._mode === 'ADSR')) {
      // Determine target level to which we will decay
      var decayTargetLevel;
      if (this._mode === 'AD') {
        decayTargetLevel = INITIAL_LEVEL;
      } else {
        decayTargetLevel = this._sustainFraction*this._attackLevel;
      }

      this._scheduleSegment(decayTargetLevel, this._decayShape, this._decayRate);
    }

    // NOTE: Sustain does not need to be scheduled
  } else {
    this._scheduleSegment(INITIAL_LEVEL, this._releaseShape, this._releaseRate);
  }
};

EnvGen.prototype.gateOn = function(time) {
  this.gate(true, time);
};

EnvGen.prototype.gateOff = function(time) {
  this.gate(false, time);
};

module.exports = EnvGen;
<commit_msg>Fix big bug with linear ramps. Restructure a bit<commit_after>'use strict'

var nextafter = require('math-float64-nextafter');

function assert(v) {
  if (!v) {
    throw new Error('Assertion error');
  }
}

var INITIAL_LEVEL = 0; // It doesn't seem useful for this to be a setting

function EnvGen(audioContext, targetParam) {
  // Support instantiating w/o new
  if (!(this instanceof EnvGen)) {
    return new EnvGen(audioContext, targetParam);
  }

  this._audioContext = audioContext;
  this._targetParam = targetParam;

  var _this = this;

  Object.defineProperty(this, 'mode', {
    get: function() { return _this._mode; },
    set: function(value) {
      if (_this.MODES.indexOf(value) >= 0) {
        // If we're currently in a 'sustain' state, and we switched into AD mode,
        // we would get stuck in sustain state. So just to be safe, whenever mode
        // is changed we fake a gate-off signal.
        _this.gate(false, nextafter(Math.max(this._lastGateTime, audioContext.currentTime), Infinity));

        _this._mode = value;
      }
    }
  });

  Object.defineProperty(this, 'attackShape', {
    get: function() { return _this._attackShape; },
    set: function(value) {
      if (_this.ATTACK_SHAPES.indexOf(value) >= 0) {
        _this._attackShape = value;
      }
    }
  });

  Object.defineProperty(this, 'attackRate', {
    get: function() { return _this._attackRate; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value > 0)) {
        _this._attackRate = value;
      }
    }
  });

  Object.defineProperty(this, 'attackLevel', {
    get: function() { return _this._attackLevel; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value)) {
        _this._attackLevel = value;
      }
    }
  });

  Object.defineProperty(this, 'decayShape', {
    get: function() { return _this._decayShape; },
    set: function(value) {
      if (_this.DECAY_SHAPES.indexOf(value) >= 0) {
        _this._decayShape = value;
      }
    }
  });

  Object.defineProperty(this, 'decayRate', {
    get: function() { return _this._decayRate; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value > 0)) {
        _this._decayRate = value;
      }
    }
  });

  Object.defineProperty(this, 'sustainFraction', {
    get: function() { return _this._sustainFraction; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value >= 0) && (value <= 1)) {
        _this._sustainFraction = value;
      }
    }
  });

  Object.defineProperty(this, 'releaseShape', {
    get: function() { return _this._releaseShape; },
    set: function(value) {
      if (_this.RELEASE_SHAPES.indexOf(value) >= 0) {
        _this._releaseShape = value;
      }
    }
  });

  Object.defineProperty(this, 'releaseRate', {
    get: function() { return _this._releaseRate; },
    set: function(value) {
      if ((typeof(value) === 'number') && !isNaN(value) && (value > 0)) {
        _this._releaseRate = value;
      }
    }
  });

  // Default settings
  this._mode = 'ADSR';
  this._attackShape = this.LINEAR;
  this._attackRate = 2;
  this._attackLevel = 1.0;
  this._decayShape = this.EXPONENTIAL;
  this._decayRate = 1;
  this._sustainFraction = 0.5;
  this._releaseShape = this.EXPONENTIAL;
  this._releaseRate = 1;

  this._targetParam.value = INITIAL_LEVEL;

  // Each segment has properties:
  //  beginTime: inclusive
  //  endTime: exclusive. may be +inf
  //  beginValue
  //  endValue
  //  shape: this.LINEAR, etc.
  //  rate: abs(slope-of-value) for LINEAR, abs(slope-of-log(value)) for EXPONENTIAL shapes
  this._scheduledSegments = [];

  // Track info about last gate we received
  this._lastGateTime = audioContext.currentTime;
  this._lastGateState = false;
}

EnvGen.prototype.MODES = ['AD', 'ASR', 'ADSR'];

EnvGen.prototype.LINEAR = 'L';
EnvGen.prototype.EXPONENTIAL = 'E';

EnvGen.prototype.ATTACK_SHAPES = [EnvGen.prototype.LINEAR];
EnvGen.prototype.DECAY_SHAPES = [EnvGen.prototype.LINEAR, EnvGen.prototype.EXPONENTIAL];
EnvGen.prototype.RELEASE_SHAPES = [EnvGen.prototype.LINEAR, EnvGen.prototype.EXPONENTIAL];

EnvGen.prototype._interruptScheduledSegments = function(time) {
  var interruptValue;

  if (!this._scheduledSegments.length) {
    // If there are no scheduled segments, that means envelope should be at zero
    interruptValue = 0;
  } else {
    // Sanity check: Scheduled segments should not start in the future
    assert(time >= this._scheduledSegments[0].beginTime);

    // Find what scheduled segment (if any) would be active at given time
    var activeIdx; // index into _scheduledSegments of one that is active at given time, if any
    for (var i = 0; i < this._scheduledSegments.length; i++) {
      var seg = this._scheduledSegments[i];
      if ((time >= seg.beginTime) && (time < seg.endTime)) {
        activeIdx = i;
        break;
      }
    }

    if (activeIdx === undefined) {
      // This must mean that time is after last scheduled segment
      var lastSeg = this._scheduledSegments[this._scheduledSegments.length-1];
      assert(time >= lastSeg.endTime); // sanity check
      interruptValue = lastSeg.endValue;
    } else {
      // If we got this far, then the given time falls within a scheduled segment
      var activeSeg = this._scheduledSegments[activeIdx];

      // Determine the mid-segment value at the given time
      if (activeSeg.shape === this.LINEAR) {
        assert(activeSeg.endTime !== Infinity); // sanity check
        // LERP
        // TODO: should be equal to activeSeg.beginValue + activeSeg.rate*(time - activeSeg.beginTime). should we check or use that instead?
        interruptValue = activeSeg.beginValue + ((time - activeSeg.beginTime)/(activeSeg.endTime - activeSeg.beginTime))*(activeSeg.endValue - activeSeg.beginValue);

        // Replace previous linear ramp with new ramp that ends at interrupt time.
        // This is important because the ramp could be currently playing, so we don't want to remove it.
        this._targetParam.linearRampToValueAtTime(interruptValue, time);
      } else if (activeSeg.shape === this.EXPONENTIAL) {
        interruptValue = activeSeg.endValue + (activeSeg.beginValue - activeSeg.endValue)*Math.exp(activeSeg.rate*(activeSeg.beginTime - time));

        // Set anchor point for further automations to continue from
        this._targetParam.setValueAtTime(interruptValue, time);
      } else {
        assert(false);
      }
    }
  }

  assert(interruptValue !== undefined); // sanity check

  // Cancel all scheduled changes after the interrupt time.
  // Note that nextafter is necessary here because cancelScheduledValues cancels events
  // with times >= the given time, so if we just passed time it would cancel whatever event
  // that we set above.
  this._targetParam.cancelScheduledValues(nextafter(time, Infinity));

  // Reinit scheduled segments array with a 'dummy' segment to simplify other code.
  // The dummy segment has zero duration but marks the value at interrupt time.
  this._scheduledSegments = [{
    beginTime: time,
    endTime: time,
    beginValue: interruptValue,
    endValue: interruptValue,
    shape: this.LINEAR,
  }];
};

EnvGen.prototype._scheduleSegment = function(endValue, shape, rate) {
  assert(this._scheduledSegments.length > 0);
  var lastSeg = this._scheduledSegments[this._scheduledSegments.length - 1];

  // It doesn't make sense to schedule a segment after an infinite one
  assert(lastSeg.endTime !== Infinity);

  if (lastSeg.endValue === endValue) {
    // We can just skip this segment since value is already there
    return;
  }

  // Based on shape, compute end time and call appropriate scheduling method
  var endTime;
  if (rate === Infinity) {
    // Special case this
    endTime = lastSeg.endTime;
    this._targetParam.setValueAtTime(endValue, endTime); // Spec says this should work. "If one of these events is added at a time where there is already one or more events, then it will be placed in the list after them, but before events whose times are after the event."
  } else {
    switch (shape) {
      case this.LINEAR:
        endTime = lastSeg.endTime + Math.abs((endValue - lastSeg.endValue)/rate);
        this._targetParam.linearRampToValueAtTime(endValue, endTime);
        break;

      case this.EXPONENTIAL:
        endTime = Infinity;
        this._targetParam.setTargetAtTime(endValue, lastSeg.endTime, 1.0/rate);
        break;

      default:
        assert(false);
    }
  }

  this._scheduledSegments.push({
    shape: shape,
    beginTime: lastSeg.endTime,
    endTime: endTime,
    beginValue: lastSeg.endValue,
    endValue: endValue,
    rate: rate,
  });
};

EnvGen.prototype.gate = function(on, time) {
  // Note the current AudioContext time
  var ct = this._audioContext.currentTime;

  // Default time parameter to current time
  time = (time === undefined) ? ct : time;

  if (time < this._lastGateTime) {
    // Gates can only have times >= the times of previously supplied gates.
    // If we receive a bad one, log a warning and ignore
    console.warn('Received gate with time earlier than a previous gate');
    return;
  }

  this._lastGateTime = time;
  this._lastGateState = on;

  // Special case: In AD mode, we ignore gate-off
  if ((this._mode === 'AD') && !on) {
    return;
  }

  // Interrupt scheduled segments at the gate time
  this._interruptScheduledSegments(time);

  if (on) {
    // Schedule attack
    this._scheduleSegment(this._attackLevel, this._attackShape, this._attackRate);

    // Schedule decay, if needed
    if ((this._mode === 'AD') || (this._mode === 'ADSR')) {
      // Determine target level to which we will decay
      var decayTargetLevel;
      if (this._mode === 'AD') {
        decayTargetLevel = INITIAL_LEVEL;
      } else {
        decayTargetLevel = this._sustainFraction*this._attackLevel;
      }

      this._scheduleSegment(decayTargetLevel, this._decayShape, this._decayRate);
    }

    // NOTE: Sustain does not need to be scheduled
  } else {
    this._scheduleSegment(INITIAL_LEVEL, this._releaseShape, this._releaseRate);
  }
};

EnvGen.prototype.gateOn = function(time) {
  this.gate(true, time);
};

EnvGen.prototype.gateOff = function(time) {
  this.gate(false, time);
};

module.exports = EnvGen;
<|endoftext|>