<commit_before>'use strict';

var MagicCrop = function () {

    // Attempts to automatically calculate the cropping bound { minX, minY, maxX, maxY }
    // for the given the ImageData (https://developer.mozilla.org/en/docs/Web/API/ImageData) that contains a photo within.
    //
    // Algorithm is:
    // 1. Calculate the 3 most popular colors across the entire image. Consider these our *background colors*.
    // 2. Sample some "randomly" distributed points around the image, and from each point work in all four directions towards each edge.
    // 3. If, in any direction, we hit a *background color*, OR we make it to the *edge*, then store this as a potential cropping bound for that direction.
    // 4. For each of the 4 potential bounds calculated above, choose the most popular (mode) for each edge, to represent our final crop region.
    // 5. Apply this crop region to our original image.
    //
    // In the iOS Crop Magic app, this function is called from some seriously bad ass Angular Webworker library, as described here:
    // https://github.com/mattslocum/ng-webworker
    // For use with this library, it's required/advantageous if our background function:
    // 1. is *fully self contained*, so all dependent functions are defined inside it, and
    // 2. can take advantage of *WebWorker transferable objects*: http://w3c.github.io/html/infrastructure.html#transferable-objects.
    //
    // This allows us to pass off the cropping of each image to a backgound WebWorker, and all so we can try to show
    // a *semi-decent GPU accelerated* CSS spinner as the image processing animation...
    // (...with admitted stutter as the image is transferred to and from the background thread!).
    this.calcCroppingBounds = function (imageBytes, width, height) {

        // todo: Make these maguc numbers configurable? Through trial and error they work well for the general case...

        // The number of potential background color candidates to consider.
        var bgColorCount = 3;
        // When calculating the background color(s), for speed sample only 1 out of every n pixels - it's all the same really.
        var bgColorSampleRatio = 100;
        // When scanning the image for bounds, do not sample this outer fraction of the image, cos that's usually where the solid
        // headers/footers lie.
        var boundsDetectionPaddingFraction = 0.2;
        // When scanning the image for bounds, take this many samples inside the padding described above.
        var boundsDetectionSampleCount = 15;
        // If we determine a bound is not the edge of the image, crop an additional number of pixels to avoid any anti-aliasing artifacts.
        var antiAliasFiddle = 2;

        // reconstruct the ImageData object that we had to pass in as primitives
        var imageData = {
            data: new Uint8ClampedArray(imageBytes),
            width: width,
            height: height
        };

        if (imageData.data.length === 0) {
            throw new Error('Empty image');
        }

        var expectedBytes = width * height * 4;
        if (expectedBytes != imageData.data.length) {
            throw new Error('Inconsistent width / height for supplied image data. Expected ' +
                expectedBytes + ' bytes but received ' + imageData.data.length);
        }

        // Converts a decimal value to a pixel data structure. 'decimal' is a really bad name.
        function toPixel(decimal) {
            return {
                r: decimal >>> 24 & 0xFF,
                g: decimal >>> 16 & 0xFF,
                b: decimal >>> 8 & 0xFF,
                a: decimal & 0xFF,
                decimal: decimal
            }
        }

        // Calculates the decimal value represented by the given RGBA components.
        function toDecimal(r, g, b, a) {
            return (r << 24) + (g << 16) + (b << 8) + a;
        }

        // Extracts the pixel from the byte array at the given offset.
        function getPixelAtOffset(bytes, offset) {
            var r = bytes[offset + 0];
            var g = bytes[offset + 1];
            var b = bytes[offset + 2];
            var a = bytes[offset + 3];

            return {
                r: r,
                g: g,
                b: b,
                a: a,
                decimal: toDecimal(r, g, b, a)
            };
        }

        // Extracts the pixel from the image for the given coordinate.
        function getPixelAt(imageData, x, y) {
            var offset = ((y * (imageData.width * 4)) + (x * 4));
            return getPixelAtOffset(imageData.data, offset);
        }

        // When passed to Array.sort, sorts numerically ascending.
        function asc(a, b) {
            return a - b;
        }

        // When passed to Array.sort, sorts numerically descending.
        function desc(a, b) {
            return b - a;
        }

        // Returns the supplied default undefined value.
        function defaultIfUndefined(val, def) {
            return typeof val === 'undefined' ? def : val;
        }

        // Takes a hash of values against their frequency and returns the most frequently occurring values.
        function calculateModes(frequencyHash, countLimit, transform) {
            var values = [], most = 0, results = [], i;

            transform = transform || function (x) {
                return x;
            };

            // create an array of the counts
            Object.keys(frequencyHash).forEach(function (key) {
                var value = frequencyHash[key];
                values.push(value);
                most = (value > most ? value : most);
            });

            // sort by descending popularity
            values.sort(desc);

            // look up the values for the most popular counts
            for (i = 0; i < countLimit; i++) {
                var count = values[i];
                var modes = [];
                Object.keys(frequencyHash).forEach(function (key) {
                    if (frequencyHash[key] === count) {
                        modes.push(transform(key));
                    }
                });
                results.push({
                    values: modes,
                    count: count
                });
            }

            return results;
        }

        // Scans the image to work out the most likely no. of bgColorCount candidates for the background color.
        function getBackgroundColors(bytes, bgColorCount) {
            var frequencyHash = {};
            var result, i, pixel, modes;

            // get the counts of each unique color
            for (i = 0; i < bytes.length; i += (4 * bgColorSampleRatio)) {
                pixel = getPixelAtOffset(bytes, i);
                if (frequencyHash[pixel.decimal]) {
                    frequencyHash[pixel.decimal]++;
                } else {
                    frequencyHash[pixel.decimal] = 1;
                }
            }

            function transform(x) {
                return toPixel(Number(x));
            }

            function map(mode) {
                return mode.values;
            }

            modes = calculateModes(frequencyHash, bgColorCount, transform)
                .map(map);

            // flatten the modes into a single array, and truncate if necessary
            result = [].concat.apply([], modes)
                .slice(0, bgColorCount);
            return result;
        }

        // Given a coordinate, works towards the edges in each direction, looking for the first
        // pixel that is considered a background color.  This is assumed to represent a bound.
        function getCroppingBoundFromPosition(imageData, bgColors, x, y) {
            var bgHash = {},
                minX, minY, maxX, maxY, width;

            // for efficiency, create a hash of our background pixel values
            bgColors.forEach(function (c) {
                bgHash[c.decimal] = true;
            });

            function isBgColor(x, y) {
                var pixel = getPixelAt(imageData, x, y);
                return typeof bgHash[pixel.decimal] !== 'undefined';
            }

            // if this pixel is a background color then ignore it
            if (isBgColor(x, y)) {
                return null;
            }

            // work from our sample pixel...

            // ... to the left
            for (minX = x; minX > 0; minX--) {
                if (isBgColor(minX - 1, y)) {
                    break;
                }
            }
            if (minX > 0) {
                minX += antiAliasFiddle;
            }

            // ... to the right
            for (maxX = x; maxX < (imageData.width - 1); maxX++) {
                if (isBgColor(maxX + 1, y)) {
                    break;
                }
            }
            if (maxX < (imageData.width - 1)) {
                maxX -= antiAliasFiddle;
            }

            // ... to the top
            for (minY = y; minY > 0; minY--) {
                if (isBgColor(x, minY - 1)) {
                    break;
                }
            }
            if (minY > 0) {
                minY += antiAliasFiddle;
            }

            // ... and finally to the bottom
            for (maxY = y; maxY < (imageData.height - 1); maxY++) {
                if (isBgColor(x, maxY + 1)) {
                    break;
                }
            }
            if (maxY < (imageData.height - 1)) {
                maxY -= antiAliasFiddle;
            }

            return {
                minX: minX,
                minY: minY,
                maxX: maxX,
                maxY: maxY
            };
        }

        // Given a length and the desired number of samples, returns an array of evenly
        // spaced coordinates along the length, ignoring any padding.
        function getSampleCoords(length, sampleCount, padding) {
            var start = padding * length;
            var end = (1 - padding) * length;
            var increment = (end - start) / (sampleCount - 1);
            var coord, coords = [];

            for (coord = start; coord <= end; coord += increment) {
                coords.push(Math.floor(coord));
            }

            return coords;
        }

        // Attempts to automatically determine the cropping bounds of an image, using the specified
        // background color(s) and number of samples.
        function getCroppingBound(imageData, bgColors) {
            var bounds = [];
            var result = {};
            var xs = getSampleCoords(imageData.width, boundsDetectionSampleCount, boundsDetectionPaddingFraction);
            var ys = getSampleCoords(imageData.height, boundsDetectionSampleCount, boundsDetectionPaddingFraction);

            // for each of the coords just calculated, sample potential cropping bounds
            xs.forEach(function (x) {
                ys.forEach(function (y) {
                    var bound = getCroppingBoundFromPosition(imageData, bgColors, x, y);
                    if (bound !== null) {
                        bounds.push(bound);
                    }
                });
            });

            // from the potentials calculated above, work out the most frequently occurring
            // bound for minX, minY, maxX and maxY
            ['min', 'max'].forEach(function (limit) {
                ['X', 'Y'].forEach(function (dimension) {

                    // build frequency hash of our potential bounds
                    var key = limit + dimension,
                        frequencyHash = {},
                        modes;
                    // create a hash of the counts of each potential bound
                    bounds.forEach(function (bound) {
                        var value = bound[key];
                        if (frequencyHash[value]) {
                            frequencyHash[value]++;
                        } else {
                            frequencyHash[value] = 1;
                        }
                    });

                    // calculate the most frequently occurring bound
                    modes = calculateModes(frequencyHash, 1, Number)[0].values;
                    // when there is a tie use either the furthest up or left for min,
                    // or the further down or right for max
                    modes.sort(limit === 'min' ? asc : desc);
                    result[key] = modes[0];

                    // that's some really hairy sh.t... I'm not sure I understand it myself or if it's even a good solution
                });
            });

            return result;
        }

        var bgColors = getBackgroundColors(imageData.data, bgColorCount);
        var bound = getCroppingBound(imageData, bgColors);

        // if we couldn't calculate one of the bounds for some reason (eg. image is too
        // small to do anything with) then just return the image at its original size
        bound.minX = defaultIfUndefined(bound.minX, 0);
        bound.minY = defaultIfUndefined(bound.minY, 0);
        bound.maxX = defaultIfUndefined(bound.maxX, imageData.width - 1);
        bound.maxY = defaultIfUndefined(bound.maxY, imageData.height - 1);
        // this feels all wrong!
        return bound;
    };

    // Draws the given HTML Image element onto a new canvas and returns the canvas.
    this.getImageData = function (imageElem) {
        var canvas = document.createElement('canvas');
        canvas.height = imageElem.height;
        canvas.width = imageElem.width;
        var context = canvas.getContext('2d');
        context.drawImage(imageElem, 0, 0);
        return canvas.getContext('2d').getImageData(0, 0, imageElem.width, imageElem.height)
    };

    // Draws a (cropped) bounded region from the given Image element onto a new canvas and returns the canvas.
    this.cropToCanvas = function (imageElem, bound) {
        var croppedCanvas = document.createElement('canvas');
        croppedCanvas.width = bound.maxX - bound.minX + 1;
        croppedCanvas.height = bound.maxY - bound.minY + 1;
        croppedCanvas.getContext('2d').drawImage(
            imageElem,
            bound.minX,
            bound.minY,
            croppedCanvas.width,
            croppedCanvas.height,
            0,
            0,
            croppedCanvas.width,
            croppedCanvas.height
        );
        return croppedCanvas;
    };

};

if (typeof module !== 'undefined') {
    module.exports = MagicCrop;
}
<commit_msg>Update magicCrop.js<commit_after>'use strict';

var MagicCrop = function () {

    // Attempts to automatically calculate the cropping bound { minX, minY, maxX, maxY }
    // for the given the ImageData (https://developer.mozilla.org/en/docs/Web/API/ImageData) that contains a photo within.
    //
    // Algorithm is:
    // 1. Calculate the 3 most popular colors across the entire image. Consider these our *background colors*.
    // 2. Sample some "randomly" distributed points around the image, and from each point work in all four directions towards each edge.
    // 3. If, in any direction, we hit a *background color*, OR we make it to the *edge*, then store this as a potential cropping bound for that direction.
    // 4. From each of the 4 sets of potential bounds calculated above, choose the most popular (mode) from each. These describe our final crop region.
    // 5. Apply this crop region to our original image.
    //
    // In the iOS Crop Magic app, this function is called from some seriously bad ass Angular Webworker library, as described here:
    // https://github.com/mattslocum/ng-webworker
    // For use with this library, it's required/advantageous if our background function:
    // 1. is *fully self contained*, so all dependent functions are defined inside it, and
    // 2. can take advantage of *WebWorker transferable objects*: http://w3c.github.io/html/infrastructure.html#transferable-objects.
    // This allows us to process the cropping of each image in the backgound, keeping the UI responsive.
    this.calcCroppingBounds = function (imageBytes, width, height) {

        // todo: Make these magic numbers configurable? Through trial and error they work well for the general case...

        // The number of potential background color candidates to consider.
        var bgColorCount = 3;
        // When calculating the background color(s), for speed sample only 1 out of every n pixels - it's all the same really.
        var bgColorSampleRatio = 100;
        // When scanning the image for bounds, do not sample this outer fraction of the image. This is usually where the solid
        // headers/footers lie.
        var boundsDetectionPaddingFraction = 0.2;
        // When scanning the image for bounds, take this many samples inside the padding described above.
        var boundsDetectionSampleCount = 15;
        // If we determine a bound is not the edge of the image, crop an additional number of pixels to avoid any anti-aliasing artifacts.
        var antiAliasFiddle = 2;

        // reconstruct the ImageData object that we had to pass in as primitives
        var imageData = {
            data: new Uint8ClampedArray(imageBytes),
            width: width,
            height: height
        };

        if (imageData.data.length === 0) {
            throw new Error('Empty image');
        }

        var expectedBytes = width * height * 4;
        if (expectedBytes != imageData.data.length) {
            throw new Error('Inconsistent width / height for supplied image data. Expected ' +
                expectedBytes + ' bytes but received ' + imageData.data.length);
        }

        // Converts a decimal value to a pixel data structure. 'decimal' is a really bad name.
        function toPixel(decimal) {
            return {
                r: decimal >>> 24 & 0xFF,
                g: decimal >>> 16 & 0xFF,
                b: decimal >>> 8 & 0xFF,
                a: decimal & 0xFF,
                decimal: decimal
            }
        }

        // Calculates the decimal value represented by the given RGBA components.
        function toDecimal(r, g, b, a) {
            return (r << 24) + (g << 16) + (b << 8) + a;
        }

        // Extracts the pixel from the byte array at the given offset.
        function getPixelAtOffset(bytes, offset) {
            var r = bytes[offset + 0];
            var g = bytes[offset + 1];
            var b = bytes[offset + 2];
            var a = bytes[offset + 3];

            return {
                r: r,
                g: g,
                b: b,
                a: a,
                decimal: toDecimal(r, g, b, a)
            };
        }

        // Extracts the pixel from the image for the given coordinate.
        function getPixelAt(imageData, x, y) {
            var offset = ((y * (imageData.width * 4)) + (x * 4));
            return getPixelAtOffset(imageData.data, offset);
        }

        // When passed to Array.sort, sorts numerically ascending.
        function asc(a, b) {
            return a - b;
        }

        // When passed to Array.sort, sorts numerically descending.
        function desc(a, b) {
            return b - a;
        }

        // Returns the supplied default undefined value.
        function defaultIfUndefined(val, def) {
            return typeof val === 'undefined' ? def : val;
        }

        // Takes a hash of values against their frequency and returns the most frequently occurring values.
        function calculateModes(frequencyHash, countLimit, transform) {
            var values = [], most = 0, results = [], i;

            transform = transform || function (x) {
                return x;
            };

            // create an array of the counts
            Object.keys(frequencyHash).forEach(function (key) {
                var value = frequencyHash[key];
                values.push(value);
                most = (value > most ? value : most);
            });

            // sort by descending popularity
            values.sort(desc);

            // look up the values for the most popular counts
            for (i = 0; i < countLimit; i++) {
                var count = values[i];
                var modes = [];
                Object.keys(frequencyHash).forEach(function (key) {
                    if (frequencyHash[key] === count) {
                        modes.push(transform(key));
                    }
                });
                results.push({
                    values: modes,
                    count: count
                });
            }

            return results;
        }

        // Scans the image to work out the most likely no. of bgColorCount candidates for the background color.
        function getBackgroundColors(bytes, bgColorCount) {
            var frequencyHash = {};
            var result, i, pixel, modes;

            // get the counts of each unique color
            for (i = 0; i < bytes.length; i += (4 * bgColorSampleRatio)) {
                pixel = getPixelAtOffset(bytes, i);
                if (frequencyHash[pixel.decimal]) {
                    frequencyHash[pixel.decimal]++;
                } else {
                    frequencyHash[pixel.decimal] = 1;
                }
            }

            function transform(x) {
                return toPixel(Number(x));
            }

            function map(mode) {
                return mode.values;
            }

            modes = calculateModes(frequencyHash, bgColorCount, transform)
                .map(map);

            // flatten the modes into a single array, and truncate if necessary
            result = [].concat.apply([], modes)
                .slice(0, bgColorCount);
            return result;
        }

        // Given a coordinate, works towards the edges in each direction, looking for the first
        // pixel that is considered a background color.  This is assumed to represent a bound.
        function getCroppingBoundFromPosition(imageData, bgColors, x, y) {
            var bgHash = {},
                minX, minY, maxX, maxY, width;

            // for efficiency, create a hash of our background pixel values
            bgColors.forEach(function (c) {
                bgHash[c.decimal] = true;
            });

            function isBgColor(x, y) {
                var pixel = getPixelAt(imageData, x, y);
                return typeof bgHash[pixel.decimal] !== 'undefined';
            }

            // if this pixel is a background color then ignore it
            if (isBgColor(x, y)) {
                return null;
            }

            // work from our sample pixel...

            // ... to the left
            for (minX = x; minX > 0; minX--) {
                if (isBgColor(minX - 1, y)) {
                    break;
                }
            }
            if (minX > 0) {
                minX += antiAliasFiddle;
            }

            // ... to the right
            for (maxX = x; maxX < (imageData.width - 1); maxX++) {
                if (isBgColor(maxX + 1, y)) {
                    break;
                }
            }
            if (maxX < (imageData.width - 1)) {
                maxX -= antiAliasFiddle;
            }

            // ... to the top
            for (minY = y; minY > 0; minY--) {
                if (isBgColor(x, minY - 1)) {
                    break;
                }
            }
            if (minY > 0) {
                minY += antiAliasFiddle;
            }

            // ... and finally to the bottom
            for (maxY = y; maxY < (imageData.height - 1); maxY++) {
                if (isBgColor(x, maxY + 1)) {
                    break;
                }
            }
            if (maxY < (imageData.height - 1)) {
                maxY -= antiAliasFiddle;
            }

            return {
                minX: minX,
                minY: minY,
                maxX: maxX,
                maxY: maxY
            };
        }

        // Given a length and the desired number of samples, returns an array of evenly
        // spaced coordinates along the length, ignoring any padding.
        function getSampleCoords(length, sampleCount, padding) {
            var start = padding * length;
            var end = (1 - padding) * length;
            var increment = (end - start) / (sampleCount - 1);
            var coord, coords = [];

            for (coord = start; coord <= end; coord += increment) {
                coords.push(Math.floor(coord));
            }

            return coords;
        }

        // Attempts to automatically determine the cropping bounds of an image, using the specified
        // background color(s) and number of samples.
        function getCroppingBound(imageData, bgColors) {
            var bounds = [];
            var result = {};
            var xs = getSampleCoords(imageData.width, boundsDetectionSampleCount, boundsDetectionPaddingFraction);
            var ys = getSampleCoords(imageData.height, boundsDetectionSampleCount, boundsDetectionPaddingFraction);

            // for each of the coords just calculated, sample potential cropping bounds
            xs.forEach(function (x) {
                ys.forEach(function (y) {
                    var bound = getCroppingBoundFromPosition(imageData, bgColors, x, y);
                    if (bound !== null) {
                        bounds.push(bound);
                    }
                });
            });

            // from the potentials calculated above, work out the most frequently occurring
            // bound for minX, minY, maxX and maxY
            ['min', 'max'].forEach(function (limit) {
                ['X', 'Y'].forEach(function (dimension) {

                    // build frequency hash of our potential bounds
                    var key = limit + dimension,
                        frequencyHash = {},
                        modes;
                    // create a hash of the counts of each potential bound
                    bounds.forEach(function (bound) {
                        var value = bound[key];
                        if (frequencyHash[value]) {
                            frequencyHash[value]++;
                        } else {
                            frequencyHash[value] = 1;
                        }
                    });

                    // calculate the most frequently occurring bound
                    modes = calculateModes(frequencyHash, 1, Number)[0].values;
                    // when there is a tie use either the furthest up or left for min,
                    // or the further down or right for max
                    modes.sort(limit === 'min' ? asc : desc);
                    result[key] = modes[0];

                    // that's some really hairy sh.t... I'm not sure I understand it myself or if it's even a good solution
                });
            });

            return result;
        }

        var bgColors = getBackgroundColors(imageData.data, bgColorCount);
        var bound = getCroppingBound(imageData, bgColors);

        // if we couldn't calculate one of the bounds for some reason (eg. image is too
        // small to do anything with) then just return the image at its original size
        bound.minX = defaultIfUndefined(bound.minX, 0);
        bound.minY = defaultIfUndefined(bound.minY, 0);
        bound.maxX = defaultIfUndefined(bound.maxX, imageData.width - 1);
        bound.maxY = defaultIfUndefined(bound.maxY, imageData.height - 1);
        // this feels all wrong!
        return bound;
    };

    // Draws the given HTML Image element onto a new canvas and returns the canvas.
    this.getImageData = function (imageElem) {
        var canvas = document.createElement('canvas');
        canvas.height = imageElem.height;
        canvas.width = imageElem.width;
        var context = canvas.getContext('2d');
        context.drawImage(imageElem, 0, 0);
        return canvas.getContext('2d').getImageData(0, 0, imageElem.width, imageElem.height)
    };

    // Draws a (cropped) bounded region from the given Image element onto a new canvas and returns the canvas.
    this.cropToCanvas = function (imageElem, bound) {
        var croppedCanvas = document.createElement('canvas');
        croppedCanvas.width = bound.maxX - bound.minX + 1;
        croppedCanvas.height = bound.maxY - bound.minY + 1;
        croppedCanvas.getContext('2d').drawImage(
            imageElem,
            bound.minX,
            bound.minY,
            croppedCanvas.width,
            croppedCanvas.height,
            0,
            0,
            croppedCanvas.width,
            croppedCanvas.height
        );
        return croppedCanvas;
    };

};

if (typeof module !== 'undefined') {
    module.exports = MagicCrop;
}
<|endoftext|>


<commit_before>
/*jslint node: true */
'use strict';

// ////////////////////////////////////////////////////////////////////////////
//
// system requirements,

var dotenv  = require ( 'dotenv'   ).config(),
    Promise = require ( 'bluebird' ),
    chalk   = require ( 'chalk'    );

    var vm = this || {};

    vm.status = true;

    vm.central_relay = require ( './services/central_relay.js' )( );

    vm.storage_agent = require ( './services/storage_agent.js' )( );
    vm.storage_proxy = require ( './services/storage_proxy.js' )( );

    vm.message_agent = require ( './services/message_agent.js' )( );
    vm.message_proxy = require ( './services/message_proxy.js' )( );

    vm.restapi_agent = require ( './services/restapi_agent.js' )( );
    vm.restapi_proxy = require ( './services/restapi_proxy.js' )( );

    function on_error_during_liftoff ( error )
    {
        if ( true === vm.status )
        {
            vm.status = false;

            vm.reason = error;
        }

        return error;
    }

    vm.central_relay.ctor ( ).then (

        function ( value )
        {
            return vm.storage_agent.ctor ( vm.central_relay );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.storage_proxy.ctor ( vm.central_relay, vm.storage_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.message_agent.ctor ( vm.central_relay, vm.storage_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.message_proxy.ctor ( vm.central_relay, vm.storage_agent, vm.message_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.restapi_agent.ctor ( vm.central_relay, vm.storage_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.restapi_proxy.ctor ( vm.central_relay, vm.storage_agent, vm.restapi_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).catch (

        function ( ex )
        {
            on_error_during_liftoff ( ex );

            console.log ( chalk.red ( 'unable to lift system' ) );
        }

    ).finally (

        function ( )
        {
            if ( true === vm.status )
            {
                console.log ( chalk.green ( 'system online' ) );

            } else
            {
                console.log ( chalk.red ( 'system offline, ', vm.reason ) );
            }

        }

    );<commit_msg>server_restapi publish for restapi_agent handshake on the line, message_name contract refactoring todo<commit_after>
/*jslint node: true */
'use strict';

// ////////////////////////////////////////////////////////////////////////////
//
// system requirements,

var dotenv  = require ( 'dotenv'   ).config(),
    Promise = require ( 'bluebird' ),
    chalk   = require ( 'chalk'    );

    var vm = this || {};

    vm.status = true;

    vm.central_relay = require ( './services/central_relay.js' )( );

    vm.storage_agent = require ( './services/storage_agent.js' )( );
    vm.storage_proxy = require ( './services/storage_proxy.js' )( );

    vm.message_agent = require ( './services/message_agent.js' )( );
    vm.message_proxy = require ( './services/message_proxy.js' )( );

    vm.restapi_agent = require ( './services/restapi_agent.js' )( );
    vm.restapi_proxy = require ( './services/restapi_proxy.js' )( );

    function on_error_during_liftoff ( error )
    {
        if ( true === vm.status )
        {
            vm.status = false;

            vm.reason = error;
        }

        return error;
    }

    vm.central_relay.ctor ( ).then (

        function ( value )
        {
            return vm.storage_agent.ctor ( vm.central_relay );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.storage_proxy.ctor ( vm.central_relay, vm.storage_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.message_agent.ctor ( vm.central_relay, vm.storage_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.message_proxy.ctor ( vm.central_relay, vm.storage_agent, vm.message_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.restapi_agent.ctor ( vm.central_relay, vm.storage_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).then (

        function ( value )
        {
            return vm.restapi_proxy.ctor ( vm.central_relay, vm.storage_agent, vm.restapi_agent );
        },
        function ( error )
        {
            throw on_error_during_liftoff ( error );
        }

    ).catch (

        function ( ex )
        {
            on_error_during_liftoff ( ex );

            console.log ( chalk.red ( 'unable to lift system' ) );
        }

    ).finally (

        function ( )
        {
            if ( true === vm.status )
            {
                vm.central_relay.publish ( 'restapi_listen', {} );

            } else
            {
                console.log ( chalk.red ( 'system offline, shutting down', vm.reason ) );

                process.exit ( 0 );
            }

        }

    );<|endoftext|>


<commit_before>var update = require("../dist/update").default;

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

function apply_update(test, desc, input, update_v, expected) {
    var orig = clone(input);

    var output = update(input, update_v);

    test.deepEqual(output, expected, "update applied correctly: " + desc);
    test.ok(input != output, "new structure created: " + desc);
    test.deepEqual(input, orig, "original not modified: " + desc);
}

function apply_update_unchanged(test, desc, input, update_v) {
  var orig = clone(input);

  var output = update(input, update_v);

  test.deepEqual(output, orig, "update applied correctly (no change): " + desc);
  test.ok(input === output, "shallow equality retained: " + desc);
}

exports.testUpdate = function(test){

// set

apply_update(test,
  "simple set",
  {},
  { a: { $set: 1} },
  { a: 1 }
);

apply_update(test,
  "nested set",
  { a: { b: 1 }, c: 2 },
  { a: { b: { '$set': 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "set, auto-vivify",
  { c: 2 },
  { a: { b: { '$set': 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "set array",
  { a: [ 0, ], },
  { a: { 0: { '$set': 9 } } },
  { a: [ 9 ] }
);

apply_update(test,
  "set array, new index",
  { a: [ 0, ], },
  { a: { 1: { '$set': 9 } } },
  { a: [ 0, 9 ] }
);

apply_update(test,
  "set array, new index leaving gap",
  { a: [ 0, ], },
  { a: { 4: { '$set': 9 } } },
  { a: [ 0,,,,9 ] }
);

// unset

apply_update(test,
  "unset",
  { a: { b: 1, z: 2 }, c: 2 },
  { a: { '$unset': 'b' } },
  { a: { z: 2 }, c: 2 }
);

apply_update(test,
  "unset multiple keys",
  { a: { b: 1, z: 2, x: 3, y: 4 }, c: 2 },
  { a: { '$unset': ['b', 'z', 'y'] } },
  { a: { x: 3 }, c: 2 }
);

apply_update(test,
  "unset auto-vivify",
  {},
  { a: { '$unset': 'b' } },
  { a: {} }
);

// merge

apply_update(test,
  "merge",
  { a: 1, b: 2 },
  { '$merge': { c: 3, d: { e: 4 } } },
  { a: 1, b: 2, c: 3, d: { e: 4 } }
);

apply_update(test,
  "merge overwrites",
  { a: 1, b: 2, c: 9 },
  { '$merge': { c: 3, d: { e: 4 } } },
  { a: 1, b: 2, c: 3, d: { e: 4 } }
);

apply_update(test,
  "merge auto-vivify",
  { a: 1, b: 2 },
  { q: { $merge: { b: 3, c: 4 } } },
  { a: 1, b: 2, q: { b: 3, c: 4 } }
);

apply_update(test,
  "merge undefined",
  { a: 1, b: 2 },
  { '$merge': { c: undefined } },
  { a: 1, b: 2, c: undefined }
);

// push

apply_update(test,
  "push",
  { a: [ 0, ], },
  { a: { '$push': [ 1, 2 ] } },
  { a: [ 0, 1, 2 ] }
);

apply_update(test,
  "push auto-vivify",
  {},
  { a: { '$push': [ 1, 2 ] } },
  { a: [ 1, 2 ] }
);

apply_update(test,
  "push auto-vivify null",
  { a: { b: null } },
  { a: { b: { '$push': [ 1, 2 ] } } },
  { a: { b: [ 1, 2 ] } }
);

// unshift

apply_update(test,
  "unshift",
  { a: [ 0 ] },
  { a: { '$unshift': [ 1, 2 ] } },
  { a: [ 1, 2, 0 ] }
);

apply_update(test,
  "unshift auto-vivify",
  {},
  { a: { '$unshift': [ 1, 2 ] } },
  { a: [ 1, 2 ] }
);

// splice

apply_update(test,
  "splice add",
  { a: [ 0, 1 ], },
  { a: { '$splice': [ [ 1, 0, 8, 9 ] ] } },
  { a: [ 0, 8, 9, 1 ] }
);

apply_update(test,
  "splice del",
  { a: [ 0, 1, 2 ] },
  { a: { '$splice': [ [ 1, 1, 8, 9 ] ] } },
  { a: [ 0, 8, 9, 2 ] }
);

apply_update(test,
  "splice multi",
  { a: [ 0, 1, 2 ] },
  { a: { '$splice': [ [ 1, 1, 8, 9 ], [ 0, 2, 6, {a: 1} ] ] } },
  { a: [ 6, { a: 1 }, 9, 2 ] }
);

apply_update(test,
  "splice auto-vivify",
  {},
  { a: { '$splice': [ [ 0, 0, 8, 9 ] ] } },
  { a: [ 8, 9 ] }
);

// apply

apply_update(test,
  "apply",
  { a: { b: 3, z: 2 }, c: 2 },
  { a: { b: { '$apply': (val) => val * 2} } },
  { a: { b: 6, z: 2 }, c: 2 }
);

apply_update(test,
  "apply, auto-vivify",
  { c: 2 },
  { a: { b: { '$apply': (val) => 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "apply, auto-vivify null",
  { a: { b: null }, c: 2 },
  { a: { b: { '$apply': (val) => 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "apply array",
  { a: [ 2 ] },
  { a: { 0: { '$apply': (val) => val * 2 } } },
  { a: [ 4 ] }
);

// Retain shallow equality

apply_update_unchanged(test,
  "simple set, no update",
  { a: 1 },
  { a: { $set: 1} }
);

apply_update_unchanged(test,
  "nested set, no update",
  { a: { b: 1 }, c: 2 },
  { a: { b: { '$set': 1 } } }
);

apply_update_unchanged(test,
  "set array, no update",
  { a: [ 9, ], },
  { a: { 0: { '$set': 9 } } }
);

apply_update_unchanged(test,
  "unset but already unset",
  { a: { b: 1 } },
  { a: { '$unset': 'c' } }
);

apply_update_unchanged(test,
  "unset multiple but already unset",
  { a: { b: 1 } },
  { a: { '$unset': ['c', 'd'] } }
);

apply_update_unchanged(test,
  "push empty array",
  { a: { b: [1, 2, 3] } },
  { a: { b: { '$push': [] } } }
);

apply_update_unchanged(test,
  "unshift empty array",
  { a: { b: [1, 2, 3] } },
  { a: { b: { '$unshift': [] } } }
);

apply_update_unchanged(test,
  "apply unchanged",
  { a: { b: 0, z: 2 }, c: 2 },
  { a: { b: { '$apply': (val) => val * 2} } }
);

apply_update_unchanged(test,
  "merge unchanged",
  { a: 1, b: 2 },
  { $merge: { b: 2 } }
);

// misc

// https://github.com/kolodny/immutability-helper/issues/21

apply_update(test,
  "merge auto-vivfy 2.1",
  { items: {} },
  { items: { 15: { '$merge': { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'} } } } },
  { items: { 15: { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'} } } }
);

apply_update(test,
  "merge auto-vivfy 2.2",
  { items: { 15: { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'} } } },
  { items: { 15: { '$merge': { 3: { id: 3, name: 'Three' } } } } },
  { items: { 15: { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'}, 3: { id: 3, name: 'Three' } } } }
);

// multi-set

apply_update(test,
  "multiple modifications in a single update",
  { a: { b: 1 } },
  { c: { '$set': 2 }, a: { '$merge': { e: 3 } } },
  { a: { b: 1, e: 3 }, c: 2 }
);

    test.done();
};
<commit_msg>add test for empty $merge<commit_after>var update = require("../dist/update").default;

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

function apply_update(test, desc, input, update_v, expected) {
    var orig = clone(input);

    var output = update(input, update_v);

    test.deepEqual(output, expected, "update applied correctly: " + desc);
    test.ok(input != output, "new structure created: " + desc);
    test.deepEqual(input, orig, "original not modified: " + desc);
}

function apply_update_unchanged(test, desc, input, update_v) {
  var orig = clone(input);

  var output = update(input, update_v);

  test.deepEqual(output, orig, "update applied correctly (no change): " + desc);
  test.ok(input === output, "shallow equality retained: " + desc);
}

exports.testUpdate = function(test){

// set

apply_update(test,
  "simple set",
  {},
  { a: { $set: 1} },
  { a: 1 }
);

apply_update(test,
  "nested set",
  { a: { b: 1 }, c: 2 },
  { a: { b: { '$set': 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "set, auto-vivify",
  { c: 2 },
  { a: { b: { '$set': 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "set array",
  { a: [ 0, ], },
  { a: { 0: { '$set': 9 } } },
  { a: [ 9 ] }
);

apply_update(test,
  "set array, new index",
  { a: [ 0, ], },
  { a: { 1: { '$set': 9 } } },
  { a: [ 0, 9 ] }
);

apply_update(test,
  "set array, new index leaving gap",
  { a: [ 0, ], },
  { a: { 4: { '$set': 9 } } },
  { a: [ 0,,,,9 ] }
);

// unset

apply_update(test,
  "unset",
  { a: { b: 1, z: 2 }, c: 2 },
  { a: { '$unset': 'b' } },
  { a: { z: 2 }, c: 2 }
);

apply_update(test,
  "unset multiple keys",
  { a: { b: 1, z: 2, x: 3, y: 4 }, c: 2 },
  { a: { '$unset': ['b', 'z', 'y'] } },
  { a: { x: 3 }, c: 2 }
);

apply_update(test,
  "unset auto-vivify",
  {},
  { a: { '$unset': 'b' } },
  { a: {} }
);

// merge

apply_update(test,
  "merge",
  { a: 1, b: 2 },
  { '$merge': { c: 3, d: { e: 4 } } },
  { a: 1, b: 2, c: 3, d: { e: 4 } }
);

apply_update(test,
  "merge overwrites",
  { a: 1, b: 2, c: 9 },
  { '$merge': { c: 3, d: { e: 4 } } },
  { a: 1, b: 2, c: 3, d: { e: 4 } }
);

apply_update(test,
  "merge auto-vivify",
  { a: 1, b: 2 },
  { q: { $merge: { b: 3, c: 4 } } },
  { a: 1, b: 2, q: { b: 3, c: 4 } }
);

apply_update(test,
  "merge undefined",
  { a: 1, b: 2 },
  { '$merge': { c: undefined } },
  { a: 1, b: 2, c: undefined }
);

// push

apply_update(test,
  "push",
  { a: [ 0, ], },
  { a: { '$push': [ 1, 2 ] } },
  { a: [ 0, 1, 2 ] }
);

apply_update(test,
  "push auto-vivify",
  {},
  { a: { '$push': [ 1, 2 ] } },
  { a: [ 1, 2 ] }
);

apply_update(test,
  "push auto-vivify null",
  { a: { b: null } },
  { a: { b: { '$push': [ 1, 2 ] } } },
  { a: { b: [ 1, 2 ] } }
);

// unshift

apply_update(test,
  "unshift",
  { a: [ 0 ] },
  { a: { '$unshift': [ 1, 2 ] } },
  { a: [ 1, 2, 0 ] }
);

apply_update(test,
  "unshift auto-vivify",
  {},
  { a: { '$unshift': [ 1, 2 ] } },
  { a: [ 1, 2 ] }
);

// splice

apply_update(test,
  "splice add",
  { a: [ 0, 1 ], },
  { a: { '$splice': [ [ 1, 0, 8, 9 ] ] } },
  { a: [ 0, 8, 9, 1 ] }
);

apply_update(test,
  "splice del",
  { a: [ 0, 1, 2 ] },
  { a: { '$splice': [ [ 1, 1, 8, 9 ] ] } },
  { a: [ 0, 8, 9, 2 ] }
);

apply_update(test,
  "splice multi",
  { a: [ 0, 1, 2 ] },
  { a: { '$splice': [ [ 1, 1, 8, 9 ], [ 0, 2, 6, {a: 1} ] ] } },
  { a: [ 6, { a: 1 }, 9, 2 ] }
);

apply_update(test,
  "splice auto-vivify",
  {},
  { a: { '$splice': [ [ 0, 0, 8, 9 ] ] } },
  { a: [ 8, 9 ] }
);

// apply

apply_update(test,
  "apply",
  { a: { b: 3, z: 2 }, c: 2 },
  { a: { b: { '$apply': (val) => val * 2} } },
  { a: { b: 6, z: 2 }, c: 2 }
);

apply_update(test,
  "apply, auto-vivify",
  { c: 2 },
  { a: { b: { '$apply': (val) => 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "apply, auto-vivify null",
  { a: { b: null }, c: 2 },
  { a: { b: { '$apply': (val) => 5 } } },
  { a: { b: 5 }, c: 2 }
);

apply_update(test,
  "apply array",
  { a: [ 2 ] },
  { a: { 0: { '$apply': (val) => val * 2 } } },
  { a: [ 4 ] }
);

// Retain shallow equality

apply_update_unchanged(test,
  "simple set, no update",
  { a: 1 },
  { a: { $set: 1} }
);

apply_update_unchanged(test,
  "nested set, no update",
  { a: { b: 1 }, c: 2 },
  { a: { b: { '$set': 1 } } }
);

apply_update_unchanged(test,
  "set array, no update",
  { a: [ 9, ], },
  { a: { 0: { '$set': 9 } } }
);

apply_update_unchanged(test,
  "unset but already unset",
  { a: { b: 1 } },
  { a: { '$unset': 'c' } }
);

apply_update_unchanged(test,
  "unset multiple but already unset",
  { a: { b: 1 } },
  { a: { '$unset': ['c', 'd'] } }
);

apply_update_unchanged(test,
  "push empty array",
  { a: { b: [1, 2, 3] } },
  { a: { b: { '$push': [] } } }
);

apply_update_unchanged(test,
  "unshift empty array",
  { a: { b: [1, 2, 3] } },
  { a: { b: { '$unshift': [] } } }
);

apply_update_unchanged(test,
  "apply unchanged",
  { a: { b: 0, z: 2 }, c: 2 },
  { a: { b: { '$apply': (val) => val * 2} } }
);

apply_update_unchanged(test,
  "merge unchanged",
  { a: 1, b: 2 },
  { $merge: { b: 2 } }
);

apply_update_unchanged(test,
  "merge unchanged",
  { a: 1, b: 2 },
  { $merge: {} }
);

// misc

// https://github.com/kolodny/immutability-helper/issues/21

apply_update(test,
  "merge auto-vivfy 2.1",
  { items: {} },
  { items: { 15: { '$merge': { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'} } } } },
  { items: { 15: { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'} } } }
);

apply_update(test,
  "merge auto-vivfy 2.2",
  { items: { 15: { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'} } } },
  { items: { 15: { '$merge': { 3: { id: 3, name: 'Three' } } } } },
  { items: { 15: { 1: { id: 1, name: 'One' }, 2: { id: 2, name: 'Two'}, 3: { id: 3, name: 'Three' } } } }
);

// multi-set

apply_update(test,
  "multiple modifications in a single update",
  { a: { b: 1 } },
  { c: { '$set': 2 }, a: { '$merge': { e: 3 } } },
  { a: { b: 1, e: 3 }, c: 2 }
);

    test.done();
};
<|endoftext|>


<commit_before>function ScreenController($rootScope, $scope, $$screenApplication, $$websocketService){
	$scope.$on("COMMUNICATION_INITIALIZED", function(event, data){
		// the communication is up, let's send something
		$$websocketService.send("Hej");
		$$websocketService.join();
	});

	$rootScope.broadcastEvent = function(eventname, args){
		//logDebug("Broadcasting controller event: '{0}'".format(eventname));
		$rootScope.$broadcast(eventname, args);
		$rootScope.$digest();
	}
}<commit_msg>join ud<commit_after>function ScreenController($rootScope, $scope, $$screenApplication, $$websocketService){
	$scope.$on("COMMUNICATION_INITIALIZED", function(event, data){
		// the communication is up, let's send something
		$$websocketService.send("Hej");
		//$$websocketService.join();
	});

	$rootScope.broadcastEvent = function(eventname, args){
		//logDebug("Broadcasting controller event: '{0}'".format(eventname));
		$rootScope.$broadcast(eventname, args);
		$rootScope.$digest();
	}
}<|endoftext|>


<commit_before>// a stateful pattern writer's trusty companion
Scribe = function() {
  this.curBeat = 0
  this.beats = []
  this.defaultVars = {}
}
_.extend(Scribe.prototype, {
  set: function(defaultVars) {
    this.defaultVars = defaultVars
    return this
  },

  play: function(vars) {
    vars = _.defaults(vars, this.defaultVars)

    if (vars.note) {
      vars.freq = Note.fromLatin(vars.note.toUpperCase()).frequency()
    }

    this.beats.push({
      beat: this.curBeat,
      duration: vars.duration,
      vars: vars
    })

    if (vars.wait) {
      this.curBeat += vars.duration
    }

    return this
  },

  wait: function(beats) {
    beats = beats || 0
    this.beats.push({beat: this.curBeat, duration: beats, vars: {}})
    this.curBeat += beats
    return this
  },

  toEvents: function() {
    return this.beats
  }
})
Scribe.extend = Backbone.View.extend

// a simple base pattern class
Pattern = function(options) {
  this.options = _.defaults(options, this.defaults)
  this.initialize.apply(this, arguments)
}
_.extend(Pattern.prototype, {
  defaults: {},
  initialize: function() {},
  events: function() {},
  generator: function() {
    if (this.ran) {
      return
    }

    var events = this.events()
    if (events.toEvents) {
      events = events.toEvents()
    }

    this.ran = true
    return events
  }
})
Pattern.extend = Backbone.View.extend

function SeekEvent(seekTime) {
  this.seekTime = seekTime
}
_.extend(SeekEvent.prototype, {
  finalize: function(event) {
    event.transport._genBaseTime = event.t - this.seekTime
    event.transport._nextIdx = null
  },

  run: function(event) {
    event.transport._displayBaseTime = event.t - this.seekTime
  }
})

// takes patterns, tempo, schedule, etc and turns them into a timeline of real node manipulating events
// someday will have seek / pause / rewind
// TODO:
// + tempo changes as events
// + incremental piece wise updating
// + cache, only regen changed patterns (deep property compare?)
// + continuous patterns
Transport = function(options) {
  this.options = _.defaults(options || {}, this.options)
  this._pauseTime = 0
  this._loopEvent = null
  this.looping = false
  this.state = 'stopped'
  this._task = null
  this.initialize.apply(this, arguments)
}
_.extend(Transport.prototype, Backbone.Events, {
  options: {
    tempo: 120,
    sequence: []
  },

  initialize: function() {},

  set: function(options) {
    _.extend(this.options, options)
    this.regen()
    this.trigger('set', options)
  },

  regen: function() {
    // timeline generator: bakes a sequence into events.
    // we try to offload as much work to here as possible because it doesn't
    // run in realtime.
    //
    // first rev (now): just brute force dump all events for all patterns
    // future: lazy incremental chunk-wise generation
    // if this gets slow it may be worth moving into a web worker
    console.time('transport regen')

    // step 1: generate sorted events from patterns
    var events = []
    _.each(this.options.sequence, function(seq) {
      if (seq.tempo) {
        events.push({
          beat: seq.start,
          tempo: seq.tempo,
          duration: 0
        })
      }

      if (seq.pattern) {
        var pattern = new (ref(seq.pattern))(seq)
        var patternStart = seq.start
        var patternEvents = pattern.generator()
        var event
        while (event = patternEvents.shift()) {
          event.beat += patternStart
          event.pattern = pattern
          event.transport = this
          if (event.vars.voice) {
            try {
              ref(event.vars.voice).initEvent(event)
            } catch (e) {}
          }

          if (seq.end && event.beat >= seq.end) {
            break
          } else {
            events.push(event)
          }

          if (!patternEvents || !patternEvents.length) {
            patternEvents = pattern.generator()
            // if the generator ended...
            if (!patternEvents || !patternEvents.length) {
              if (seq.loop && seq.end) {
                pattern = new window[seq.pattern](seq)
                patternEvents = pattern.generator()
                patternStart = event.beat + event.duration
              } else {
                break
              }
            }
          }
        }
      }
    }, this)

    events.sort(function(a, b) {
      if (a.beat < b.beat) {
        return -1
      } else if (a.beat > b.beat) {
        return 1
      } else {
        if (a.tempo) {
          return -1
        } else if (b.tempo) {
          return 1
        } else {
          return 0
        }
      }
    })
    this.events = events

    // step 2: bake beats into seconds and figure out song duration
    // TODO: create index of beat -> time?
    var genBeat = 0
    var genTime = 0
    var genTempo = 0
    _.each(events, function(event) {
      var deltaBeat = event.beat - genBeat
      event.dt = genTime += this.t(deltaBeat, genTempo)  // B-)
      if (event.tempo) {
        // TODO: perhaps make this an event.gen() function?
        genTempo = event.tempo
      }
      genBeat = event.beat
    }, this)

    var lastEvent = _.last(events)
    this.endBeat = lastEvent.beat + lastEvent.duration
    this.duration = lastEvent.dt + this.t(lastEvent.duration, genTempo)

    // persist loop event
    if (this._loopEvent) {
      this._loopEvent.dt = this.duration
      events.push(this._loopEvent)
    }

    // re-run the generator with the new events
    // TODO: generate tempo map and use to determine beat so that tempo edits
    // retain seek position
    this.seek(Math.min(this.playbackTime(), this.duration))

    console.timeEnd('transport regen')
    this.trigger('regen')
  },

  t: function(beats, tempo) {
    tempo = tempo || this.options.tempo
    var beatLen = 60 /* seconds */ / tempo
    return beats * beatLen
  },

  genAhead: .25,
  generator: function() {
    // event scheduler "generator" -- called in real time by scheduler to
    // re-fill buffer of next events to play.

    var now = jam.scheduler.now()

    // collect the next batch of events
    var event
    var scheduleEvents = []
    var stop = false
    while (true) {
      // if we don't have a _nextIdx (from previous generator iterations), we
      // need to determine the next event to feed by finding the next event
      // after our current generation time. binary search to the rescue!
      if (this._nextIdx == null) {
        var genTime = jam.scheduler.now() - this._genBaseTime
        this._nextIdx = _.sortedIndex(this.events, {dt: genTime}, 'dt')
      }

      // check for end of transport
      if (this._nextIdx >= this.events.length) {
        stop = true
        break
      }

      // FIXME: we need to clone for the corner case in which events are
      // touched multiple times in this loop. this only happens for really
      // short loops.
      event = _.clone(this.events[this._nextIdx])
      event.t = this._genBaseTime + event.dt

      // stop generating if we've passed our lookahead point
      if (event.t > now + this.genAhead * 2) {
        break
      }

      this._nextIdx++
      if (event.finalize) { event.finalize(event) }
      scheduleEvents.push(event)
    }

    if (stop) {
      // schedule a stop at the appropriate real time and stop generating.
      scheduleEvents.push({
        t: this._genBaseTime + this.duration,
        run: _.bind(function() {
          this.stop()
        }, this)
      })
    } else {
      // schedule next run (don't let our buffer run out)
      scheduleEvents.push({
        t: now + this.genAhead,
        run: _.bind(function() {
          jam.scheduler.runTask(this._task)
        }, this)
      })
    }

    return scheduleEvents
  },

  playbackTime: function() {
    if (this.state == 'playing') {
      return jam.scheduler.now() - this._displayBaseTime
    } else {
      return this._pauseTime
    }
  },

  _stop: function() {
    if (this._task) {
      if (this.out) {
        this.out.disconnect(0)
        this.out = null
      }
      jam.scheduler.stop(this._task)
      this._task = null
      this._pauseTime = this.playbackTime()
      this._genBaseTime = this._displayBaseTime = null
      this.state = 'paused'
      this.trigger('stop')
    }
  },

  play: function() {
    this._stop()

    this._genBaseTime = this._displayBaseTime = jam.scheduler.now() - this._pauseTime
    this._nextIdx = null

    // Web Audio scheduling grace period; see Voice for more details
    this._genBaseTime += Voice._scheduleFudge

    this.out = ctx.createGainNode()
    connect(this.out, jam.out)

    this._task = jam.scheduler.start(_.bind(this.generator, this))
    this.state = 'playing'
    this.trigger('start')
  },

  pause: function() {
    this._stop()
  },

  seek: function(seconds) {
    var wasPlaying = this.state == 'playing'
    this._stop()
    this._pauseTime = seconds
    if (wasPlaying) {
      this.play()
    }
    this.trigger('seek')
  },

  stop: function() {
    this._stop()
    this.seek(0)
    this.state = 'stopped'
  },

  setLoop: function(looping) {
    this.looping = looping
    if (looping) {
      this._loopEvent = new SeekEvent(0)
      this._loopEvent.dt = this.duration
      this._loopEvent.transport = this
      this.events.push(this._loopEvent)
    } else {
      this.events.splice(_.indexOf(this.events, this._loopEvent), 1)
      this._loopEvent = null
    }
    this.trigger('change', 'loop', this.looping)
  }
})
Transport.extend = Backbone.View.extend
<commit_msg>Rename transport 'halt' event to be more apt.<commit_after>// a stateful pattern writer's trusty companion
Scribe = function() {
  this.curBeat = 0
  this.beats = []
  this.defaultVars = {}
}
_.extend(Scribe.prototype, {
  set: function(defaultVars) {
    this.defaultVars = defaultVars
    return this
  },

  play: function(vars) {
    vars = _.defaults(vars, this.defaultVars)

    if (vars.note) {
      vars.freq = Note.fromLatin(vars.note.toUpperCase()).frequency()
    }

    this.beats.push({
      beat: this.curBeat,
      duration: vars.duration,
      vars: vars
    })

    if (vars.wait) {
      this.curBeat += vars.duration
    }

    return this
  },

  wait: function(beats) {
    beats = beats || 0
    this.beats.push({beat: this.curBeat, duration: beats, vars: {}})
    this.curBeat += beats
    return this
  },

  toEvents: function() {
    return this.beats
  }
})
Scribe.extend = Backbone.View.extend

// a simple base pattern class
Pattern = function(options) {
  this.options = _.defaults(options, this.defaults)
  this.initialize.apply(this, arguments)
}
_.extend(Pattern.prototype, {
  defaults: {},
  initialize: function() {},
  events: function() {},
  generator: function() {
    if (this.ran) {
      return
    }

    var events = this.events()
    if (events.toEvents) {
      events = events.toEvents()
    }

    this.ran = true
    return events
  }
})
Pattern.extend = Backbone.View.extend

function SeekEvent(seekTime) {
  this.seekTime = seekTime
}
_.extend(SeekEvent.prototype, {
  finalize: function(event) {
    event.transport._genBaseTime = event.t - this.seekTime
    event.transport._nextIdx = null
  },

  run: function(event) {
    event.transport._displayBaseTime = event.t - this.seekTime
  }
})

// takes patterns, tempo, schedule, etc and turns them into a timeline of real node manipulating events
// someday will have seek / pause / rewind
// TODO:
// + tempo changes as events
// + incremental piece wise updating
// + cache, only regen changed patterns (deep property compare?)
// + continuous patterns
Transport = function(options) {
  this.options = _.defaults(options || {}, this.options)
  this._pauseTime = 0
  this._loopEvent = null
  this.looping = false
  this.state = 'stopped'
  this._task = null
  this.initialize.apply(this, arguments)
}
_.extend(Transport.prototype, Backbone.Events, {
  options: {
    tempo: 120,
    sequence: []
  },

  initialize: function() {},

  set: function(options) {
    _.extend(this.options, options)
    this.regen()
    this.trigger('set', options)
  },

  regen: function() {
    // timeline generator: bakes a sequence into events.
    // we try to offload as much work to here as possible because it doesn't
    // run in realtime.
    //
    // first rev (now): just brute force dump all events for all patterns
    // future: lazy incremental chunk-wise generation
    // if this gets slow it may be worth moving into a web worker
    console.time('transport regen')

    // step 1: generate sorted events from patterns
    var events = []
    _.each(this.options.sequence, function(seq) {
      if (seq.tempo) {
        events.push({
          beat: seq.start,
          tempo: seq.tempo,
          duration: 0
        })
      }

      if (seq.pattern) {
        var pattern = new (ref(seq.pattern))(seq)
        var patternStart = seq.start
        var patternEvents = pattern.generator()
        var event
        while (event = patternEvents.shift()) {
          event.beat += patternStart
          event.pattern = pattern
          event.transport = this
          if (event.vars.voice) {
            try {
              ref(event.vars.voice).initEvent(event)
            } catch (e) {}
          }

          if (seq.end && event.beat >= seq.end) {
            break
          } else {
            events.push(event)
          }

          if (!patternEvents || !patternEvents.length) {
            patternEvents = pattern.generator()
            // if the generator ended...
            if (!patternEvents || !patternEvents.length) {
              if (seq.loop && seq.end) {
                pattern = new window[seq.pattern](seq)
                patternEvents = pattern.generator()
                patternStart = event.beat + event.duration
              } else {
                break
              }
            }
          }
        }
      }
    }, this)

    events.sort(function(a, b) {
      if (a.beat < b.beat) {
        return -1
      } else if (a.beat > b.beat) {
        return 1
      } else {
        if (a.tempo) {
          return -1
        } else if (b.tempo) {
          return 1
        } else {
          return 0
        }
      }
    })
    this.events = events

    // step 2: bake beats into seconds and figure out song duration
    // TODO: create index of beat -> time?
    var genBeat = 0
    var genTime = 0
    var genTempo = 0
    _.each(events, function(event) {
      var deltaBeat = event.beat - genBeat
      event.dt = genTime += this.t(deltaBeat, genTempo)  // B-)
      if (event.tempo) {
        // TODO: perhaps make this an event.gen() function?
        genTempo = event.tempo
      }
      genBeat = event.beat
    }, this)

    var lastEvent = _.last(events)
    this.endBeat = lastEvent.beat + lastEvent.duration
    this.duration = lastEvent.dt + this.t(lastEvent.duration, genTempo)

    // persist loop event
    if (this._loopEvent) {
      this._loopEvent.dt = this.duration
      events.push(this._loopEvent)
    }

    // re-run the generator with the new events
    // TODO: generate tempo map and use to determine beat so that tempo edits
    // retain seek position
    this.seek(Math.min(this.playbackTime(), this.duration))

    console.timeEnd('transport regen')
    this.trigger('regen')
  },

  t: function(beats, tempo) {
    tempo = tempo || this.options.tempo
    var beatLen = 60 /* seconds */ / tempo
    return beats * beatLen
  },

  genAhead: .25,
  generator: function() {
    // event scheduler "generator" -- called in real time by scheduler to
    // re-fill buffer of next events to play.

    var now = jam.scheduler.now()

    // collect the next batch of events
    var event
    var scheduleEvents = []
    var stop = false
    while (true) {
      // if we don't have a _nextIdx (from previous generator iterations), we
      // need to determine the next event to feed by finding the next event
      // after our current generation time. binary search to the rescue!
      if (this._nextIdx == null) {
        var genTime = jam.scheduler.now() - this._genBaseTime
        this._nextIdx = _.sortedIndex(this.events, {dt: genTime}, 'dt')
      }

      // check for end of transport
      if (this._nextIdx >= this.events.length) {
        stop = true
        break
      }

      // FIXME: we need to clone for the corner case in which events are
      // touched multiple times in this loop. this only happens for really
      // short loops.
      event = _.clone(this.events[this._nextIdx])
      event.t = this._genBaseTime + event.dt

      // stop generating if we've passed our lookahead point
      if (event.t > now + this.genAhead * 2) {
        break
      }

      this._nextIdx++
      if (event.finalize) { event.finalize(event) }
      scheduleEvents.push(event)
    }

    if (stop) {
      // schedule a stop at the appropriate real time and stop generating.
      scheduleEvents.push({
        t: this._genBaseTime + this.duration,
        run: _.bind(function() {
          this.stop()
        }, this)
      })
    } else {
      // schedule next run (don't let our buffer run out)
      scheduleEvents.push({
        t: now + this.genAhead,
        run: _.bind(function() {
          jam.scheduler.runTask(this._task)
        }, this)
      })
    }

    return scheduleEvents
  },

  playbackTime: function() {
    if (this.state == 'playing') {
      return jam.scheduler.now() - this._displayBaseTime
    } else {
      return this._pauseTime
    }
  },

  _stop: function() {
    if (this._task) {
      if (this.out) {
        this.out.disconnect(0)
        this.out = null
      }
      jam.scheduler.stop(this._task)
      this._task = null
      this._pauseTime = this.playbackTime()
      this._genBaseTime = this._displayBaseTime = null
      this.state = 'paused'
      this.trigger('halt')
    }
  },

  play: function() {
    this._stop()

    this._genBaseTime = this._displayBaseTime = jam.scheduler.now() - this._pauseTime
    this._nextIdx = null

    // Web Audio scheduling grace period; see Voice for more details
    this._genBaseTime += Voice._scheduleFudge

    this.out = ctx.createGainNode()
    connect(this.out, jam.out)

    this._task = jam.scheduler.start(_.bind(this.generator, this))
    this.state = 'playing'
    this.trigger('start')
  },

  pause: function() {
    this._stop()
  },

  seek: function(seconds) {
    var wasPlaying = this.state == 'playing'
    this._stop()
    this._pauseTime = seconds
    if (wasPlaying) {
      this.play()
    }
    this.trigger('seek')
  },

  stop: function() {
    this._stop()
    this.seek(0)
    this.state = 'stopped'
  },

  setLoop: function(looping) {
    this.looping = looping
    if (looping) {
      this._loopEvent = new SeekEvent(0)
      this._loopEvent.dt = this.duration
      this._loopEvent.transport = this
      this.events.push(this._loopEvent)
    } else {
      this.events.splice(_.indexOf(this.events, this._loopEvent), 1)
      this._loopEvent = null
    }
    this.trigger('change', 'loop', this.looping)
  }
})
Transport.extend = Backbone.View.extend
<|endoftext|>


<commit_before>// a stateful pattern writer's trusty companion
Scribe = function() {
  this.curBeat = 0
  this.beats = []
  this.defaultVars = {}
}
_.extend(Scribe.prototype, {
  set: function(defaultVars) {
    this.defaultVars = defaultVars
    return this
  },

  play: function(vars) {
    vars = _.defaults(vars, this.defaultVars)

    if (vars.note) {
      vars.freq = Note.fromLatin(vars.note.toUpperCase()).frequency()
    }

    this.beats.push({
      beat: this.curBeat,
      vars: vars
    })

    if (vars.wait && vars.duration) {
      this.wait(vars.duration)
    }

    return this
  },

  wait: function(beats) {
    beats = beats || 0
    this.beats.push({beat: this.curBeat, vars: {duration: beats}})
    this.curBeat += beats
    return this
  },

  toEvents: function() {
    return this.beats
  }
})
Scribe.extend = Backbone.View.extend

// a simple base pattern class
Pattern = function(options) {
  this.options = _.defaults(options, this.defaults)
  this.initialize.apply(this, arguments)
}
_.extend(Pattern.prototype, {
  defaults: {},
  initialize: function() {},
  events: function() {},
  generator: function() {
    if (this.ran) {
      return
    }

    var events = this.events()
    if (events.toEvents) {
      events = events.toEvents()
    }
    _.each(events, function(event) {
      event.run = this.runEvent
      event.scheduled = true  // TODO: what should control this?
    }, this)
    this.ran = true
    return events
  },
  runEvent: function(event) {
    if (!event.vars.voice) {
      return
    }
    var v = new window[event.vars.voice](event.vars)
    connect(v.out, event.transport.out)
    v.play(event.t, event.transport.t(event.vars.duration))
  }
})
Pattern.extend = Backbone.View.extend

// takes patterns, tempo, schedule, etc and turns them into a timeline of real node manipulating events
// someday will have seek / pause / rewind
// TODO:
// + tempo changes as events
// + incremental piece wise updating
// + cache, only regen changed patterns (deep property compare?)
// + continuous patterns
Transport = function(options) {
  this.options = _.defaults(options || {}, this.options)
  this.out = ctx.createGainNode()
  this.curBeat = 0
  this.curTime = 0
  this.loopBeat = null
  this._task = null
  this.initialize.apply(this, arguments)
}
_.extend(Transport.prototype, Backbone.Events, {
  options: {
    tempo: 120,
    sequence: []
  },

  initialize: function() {},

  set: function(options) {
    _.extend(this.options, options)
    this.regen()
    this.trigger('set', options)
  },

  regen: function() {
    console.time('transport regen')
    this.beatLen = 60 /* seconds */ / this.options.tempo

    // braindump: first step, just brute force dump all events for all patterns
    // second step, incremental generation
    var events = []

    _.each(this.options.sequence, function(seq) {
      var pattern = new window[seq.pattern](seq)
      var patternStart = seq.start
      var patternEvents = pattern.generator()
      var event
      while (event = patternEvents.shift()) {
        event.beat += patternStart
        event.pattern = pattern
        event.transport = this

        if (seq.end && event.beat >= seq.end) {
          break
        } else {
          events.push(event)
        }

        if (!patternEvents || !patternEvents.length) {
          patternEvents = pattern.generator()
          // if the generator ended...
          if (!patternEvents || !patternEvents.length) {
            if (seq.loop && seq.end) {
              pattern = new window[seq.pattern](seq)
              patternEvents = pattern.generator()
              patternStart = event.beat + event.vars.duration
            } else {
              break
            }
          }
        }
      }
    }, this)

    events = _.sortBy(events, 'beat')
    this.events = events

    if (this._task) {
      // FIXME: this forces a re-scan of the whole event list because we don't know where we left off.
      // I think this could be replaced with a smarter update
      this.nextEvent = 0
    }

    console.timeEnd('transport regen')
  },

  t: function(beats) {
    return beats * this.beatLen
  },

  generator: function(t) {
    console.time('transport generator')
    var newEvents = []

    while (this.nextEvent < this.events.length && this.curBeat > this.events[this.nextEvent].beat) {
      // skip events too early
      this.nextEvent++
    }

    // feed events for the next beat
    if (this.loopBeat == 'end' && this.nextEvent >= this.events.length) {
      this.curBeat = 0
      this.nextEvent = 0
    }

    while (this.nextEvent < this.events.length && (event = this.events[this.nextEvent]).beat < this.curBeat + 1) {
      event.t = this.curTime + this.t(event.beat - this.curBeat)
      newEvents.push(event)
      this.nextEvent++
    }

    // if we didn't generate any events, step forward in time and wait til next beat window
    if (!newEvents.length && this.nextEvent < this.events.length) {
      // a no-op event makes the scheduler will wait until it before generating more events
      newEvents.push({
        t: this.curTime,
        run: function() {}
      })
    }

    this.curBeat++
    this.curTime += this.t(1)

    console.timeEnd('transport generator')

    return newEvents
  },

  _stop: function() {
    if (this._task) {
      jam.scheduler.stop(this._task)
    }
  },

  play: function() {
    this.regen()
    this.curBeat = 0
    this.curTime = jam.scheduler.now()
    this.nextEvent = 0
    this._stop()
    this._task = jam.scheduler.start(_.bind(this.generator, this))
  },

  stop: function() {
    this._stop()
  },

  loop: function() {
    this.loopBeat = 'end'
    this.play()
  }
})
Transport.extend = Backbone.View.extend
<commit_msg>Fix grammar.<commit_after>// a stateful pattern writer's trusty companion
Scribe = function() {
  this.curBeat = 0
  this.beats = []
  this.defaultVars = {}
}
_.extend(Scribe.prototype, {
  set: function(defaultVars) {
    this.defaultVars = defaultVars
    return this
  },

  play: function(vars) {
    vars = _.defaults(vars, this.defaultVars)

    if (vars.note) {
      vars.freq = Note.fromLatin(vars.note.toUpperCase()).frequency()
    }

    this.beats.push({
      beat: this.curBeat,
      vars: vars
    })

    if (vars.wait && vars.duration) {
      this.wait(vars.duration)
    }

    return this
  },

  wait: function(beats) {
    beats = beats || 0
    this.beats.push({beat: this.curBeat, vars: {duration: beats}})
    this.curBeat += beats
    return this
  },

  toEvents: function() {
    return this.beats
  }
})
Scribe.extend = Backbone.View.extend

// a simple base pattern class
Pattern = function(options) {
  this.options = _.defaults(options, this.defaults)
  this.initialize.apply(this, arguments)
}
_.extend(Pattern.prototype, {
  defaults: {},
  initialize: function() {},
  events: function() {},
  generator: function() {
    if (this.ran) {
      return
    }

    var events = this.events()
    if (events.toEvents) {
      events = events.toEvents()
    }
    _.each(events, function(event) {
      event.run = this.runEvent
      event.scheduled = true  // TODO: what should control this?
    }, this)
    this.ran = true
    return events
  },
  runEvent: function(event) {
    if (!event.vars.voice) {
      return
    }
    var v = new window[event.vars.voice](event.vars)
    connect(v.out, event.transport.out)
    v.play(event.t, event.transport.t(event.vars.duration))
  }
})
Pattern.extend = Backbone.View.extend

// takes patterns, tempo, schedule, etc and turns them into a timeline of real node manipulating events
// someday will have seek / pause / rewind
// TODO:
// + tempo changes as events
// + incremental piece wise updating
// + cache, only regen changed patterns (deep property compare?)
// + continuous patterns
Transport = function(options) {
  this.options = _.defaults(options || {}, this.options)
  this.out = ctx.createGainNode()
  this.curBeat = 0
  this.curTime = 0
  this.loopBeat = null
  this._task = null
  this.initialize.apply(this, arguments)
}
_.extend(Transport.prototype, Backbone.Events, {
  options: {
    tempo: 120,
    sequence: []
  },

  initialize: function() {},

  set: function(options) {
    _.extend(this.options, options)
    this.regen()
    this.trigger('set', options)
  },

  regen: function() {
    console.time('transport regen')
    this.beatLen = 60 /* seconds */ / this.options.tempo

    // braindump: first step, just brute force dump all events for all patterns
    // second step, incremental generation
    var events = []

    _.each(this.options.sequence, function(seq) {
      var pattern = new window[seq.pattern](seq)
      var patternStart = seq.start
      var patternEvents = pattern.generator()
      var event
      while (event = patternEvents.shift()) {
        event.beat += patternStart
        event.pattern = pattern
        event.transport = this

        if (seq.end && event.beat >= seq.end) {
          break
        } else {
          events.push(event)
        }

        if (!patternEvents || !patternEvents.length) {
          patternEvents = pattern.generator()
          // if the generator ended...
          if (!patternEvents || !patternEvents.length) {
            if (seq.loop && seq.end) {
              pattern = new window[seq.pattern](seq)
              patternEvents = pattern.generator()
              patternStart = event.beat + event.vars.duration
            } else {
              break
            }
          }
        }
      }
    }, this)

    events = _.sortBy(events, 'beat')
    this.events = events

    if (this._task) {
      // FIXME: this forces a re-scan of the whole event list because we don't know where we left off.
      // I think this could be replaced with a smarter update
      this.nextEvent = 0
    }

    console.timeEnd('transport regen')
  },

  t: function(beats) {
    return beats * this.beatLen
  },

  generator: function(t) {
    console.time('transport generator')
    var newEvents = []

    while (this.nextEvent < this.events.length && this.curBeat > this.events[this.nextEvent].beat) {
      // skip events too early
      this.nextEvent++
    }

    // feed events for the next beat
    if (this.loopBeat == 'end' && this.nextEvent >= this.events.length) {
      this.curBeat = 0
      this.nextEvent = 0
    }

    while (this.nextEvent < this.events.length && (event = this.events[this.nextEvent]).beat < this.curBeat + 1) {
      event.t = this.curTime + this.t(event.beat - this.curBeat)
      newEvents.push(event)
      this.nextEvent++
    }

    // if we didn't generate any events, step forward in time and wait til next beat window
    if (!newEvents.length && this.nextEvent < this.events.length) {
      // a no-op event makes the scheduler wait until it before generating more events
      newEvents.push({
        t: this.curTime,
        run: function() {}
      })
    }

    this.curBeat++
    this.curTime += this.t(1)

    console.timeEnd('transport generator')

    return newEvents
  },

  _stop: function() {
    if (this._task) {
      jam.scheduler.stop(this._task)
    }
  },

  play: function() {
    this.regen()
    this.curBeat = 0
    this.curTime = jam.scheduler.now()
    this.nextEvent = 0
    this._stop()
    this._task = jam.scheduler.start(_.bind(this.generator, this))
  },

  stop: function() {
    this._stop()
  },

  loop: function() {
    this.loopBeat = 'end'
    this.play()
  }
})
Transport.extend = Backbone.View.extend
<|endoftext|>


<commit_before>'use strict';

const payloadTypes = [
          require('./payload/payload_private'),
          require('./payload/payload_group'),
          require('./payload/payload_heartbeat'),
          require('./payload/payload_confirmation'),
          require('./payload/payload_state_changed')
      ],
      MessageBase = require('./message/message_base');

/**
 * Playload.
 *
 * Represents the incoming payloads sent externally and matching the proper message Objects.
 * Each payload arriving is initialized into a Payload object.
 */
class Payload {
    /**
     * Constructor method that stores the payload locally.
     */
    constructor(payload) {
        this.payload = payload;
        this.message = null;
    }

    /**
     * Getter for the message
     *
     * @return {Message} object, matched by the payload regex settings
     */
    getMessage() {
        if (null !== this.message)
            return this.message; // return cached response.

        let payload = this.findPayloadByType(),
            message = null;

        if (null !== payload) {
            message = payload.getMessage();
        } else {
            message = new MessageBase({
                payload: this.payload
            });
        }

        this.message = message;

        return message;
    }

    /**
     * Match payload to a payloadType
     *
     * @return {Payload} object, according to the matched response type.
     */
    findPayloadByType() {
        for (let i = 0, len = payloadTypes.length; i < len; i++) {
            let type = new payloadTypes[i](this.payload);

            if (false !== type.isItMe())
                return type;
        }

        return null;
    }

}

module.exports = Payload;<commit_msg>space removed<commit_after>'use strict';

const payloadTypes = [
          require('./payload/payload_private'),
          require('./payload/payload_group'),
          require('./payload/payload_heartbeat'),
          require('./payload/payload_confirmation'),
          require('./payload/payload_state_changed')
      ],
      MessageBase = require('./message/message_base');

/**
 * Playload.
 *
 * Represents the incoming payloads sent externally and matching the proper message Objects.
 * Each payload arriving is initialized into a Payload object.
 */
class Payload {
    /**
     * Constructor method that stores the payload locally.
     */
    constructor(payload) {
        this.payload = payload;
        this.message = null;
    }

    /**
     * Getter for the message
     *
     * @return {Message} object, matched by the payload regex settings
     */
    getMessage() {
        if (null !== this.message)
            return this.message; // return cached response.

        let payload = this.findPayloadByType(),
            message = null;

        if (null !== payload) {
            message = payload.getMessage();
        } else {
            message = new MessageBase({
                payload: this.payload
            });
        }

        this.message = message;

        return message;
    }

    /**
     * Match payload to a payloadType
     *
     * @return {Payload} object, according to the matched response type.
     */
    findPayloadByType() {
        for (let i = 0, len = payloadTypes.length; i < len; i++) {
            let type = new payloadTypes[i](this.payload);

            if (false !== type.isItMe())
                return type;
        }

        return null;
    }
}

module.exports = Payload;<|endoftext|>


<commit_before>'use strict';

/*
 * Same domain task proxy implementation for Bebras task API - v1.0 - 08/2014
 *
 * This file implements a "TaskProxyManager" object in the global scope so that
 * platforms using this file can just call
 * "myTask = TaskProxyManager.getTaskProxy(task_id)" to get the task object
 * associated to a task loaded in an iframe. Then they can just call
 * "myTask.myFunction()", with function of the API, the task object will take
 * care of message sending and receiving with the actual task.
 * 'task_id' here refers to the "id" attribute of the iframe in which a task is
 * loaded.
 *
 * It depends on jQuery.
 *
 * Currently, if you do not use the callback system, you have to make sure
 * iframe is ready before calling functions returning a result.
 * You can call task.iframe_loaded to know if it is or not.
 * This limitation doesn't apply if you use the callback system.
 *
 */

var TaskProxyManager = {
   tasks: {},
   platforms: {},
   getTaskProxy: function(idFrame, callback, force) {
      if (!force && TaskProxyManager.tasks[idFrame]) {
         callback(TaskProxyManager.tasks[idFrame]);
      } else {
         $('#'+idFrame).each(function() {
            var curTask = new Task($(this));
            TaskProxyManager.tasks[idFrame] = curTask;
         });
         callback(TaskProxyManager.tasks[idFrame]);
      }
   },
   setPlatform: function(task, platform) {
      TaskProxyManager.platforms[task.Id] = platform;
      TaskProxyManager.tasks[task.Id].setPlatform(platform);
   }
}

var taskCaller = function(task, request, content) {
   // TODO: handle case where iframe_loaded is false and caller expects a result...
   if (!task.iframe_loaded) {
      setTimeout(function() {
         taskCaller(task, request, content);
      }, 250);
   } else {
      if (task.distantTask && typeof task.distantTask[request] === 'function') {
         if (typeof content === 'string' || (typeof content === 'object' && Object.prototype.toString.call(content) !== '[object Array]')) {
            content = [content];
         }
         var functionsToTrigger = {load: true, unload:true, reloadAnswer:true, showViews: true, reloadState: true};
         if (functionsToTrigger[request] != undefined) {
            var askedCallback = content[content.length - 1];
            if (typeof askedCallback === 'function') {
               var newCallback = function() {
                  task.distantPlatform.trigger(request, content);
                  askedCallback();
               };
               content[content.length -1] = newCallback;
            }
         }
         var res = task.distantTask[request].apply(task.distantTask, content);
         return res;
      }// else {
      //   console.error("Task "+task.Id+" doesn't implement "+request);
      //}
   }
}

/*
 * Task object, created from an iframe DOM element
 */
function Task(iframe) {
   this.iframe = iframe;
   this.iframe_loaded = false;
   this.distantTask = null;
   this.Id = iframe.attr('id')
   this.elementsLoaded = false;
   this.platform = null;
   this.setPlatform = function(platform) {
      this.platform = platform;
      if (this.iframe_loaded) {
         this.distantPlatform.setPlatform(platform);
      }
   }
   var that = this;
   // checking if task is already available
   if (that.iframe[0].contentWindow.task) {
      that.iframe_loaded = true;
      that.distantTask = that.iframe[0].contentWindow.task;
      that.distantPlatform = that.iframe[0].contentWindow.platform;
      if (that.platform != null) {
         that.distantPlatform.setPlatform(that.platform);
      }
   } else {
      this.iframe.load(function() {
         that.iframe_loaded = true;
         that.distantTask = that.iframe[0].contentWindow.task;
         that.distantPlatform = that.iframe[0].contentWindow.platform;
         if (that.platform != null) {
            that.distantPlatform.setPlatform(that.platform);
         }
      });
   }
}

Task.prototype.getSourceId = function() {
   return this.Id;
}

Task.prototype.getTargetUrl = function() {
   return this.iframe.attr('src');
}

Task.prototype.getTarget = function() {
   return this.iframe[0].contentWindow;
}

Task.prototype.getDomain = function() {
   var url = this.getTargetUrl();
   return url.substr(0, url.indexOf('/', 7));
}

/**
 * Task API functions
 */
Task.prototype.load = function(views, callback) {
    return taskCaller(this, 'load', [views, callback]);
};
Task.prototype.unload = function(callback) {
    return taskCaller(this, 'unload', [callback]);
};
Task.prototype.getHeight = function(callback) {
    return taskCaller(this, 'getHeight', [callback]);
};
Task.prototype.updateToken = function(token, callback) {
    return taskCaller(this, 'updateToken', [token, callback]);
};
Task.prototype.getAnswer = function(callback) {
    return taskCaller(this, 'getAnswer', [callback]);
};
Task.prototype.reloadAnswer = function(answer, callback) {
    return taskCaller(this, 'reloadAnswer', [answer, callback]);
};
Task.prototype.getState = function(callback) {
    return taskCaller(this, 'getState', [callback]);
};
Task.prototype.reloadState = function(state, callback) {
    return taskCaller(this, 'reloadState', [state, callback]);
};
Task.prototype.displayMessage = function(type, html, isOptional) {
    return taskCaller(this, 'displayMessage', [type, html, isOptional]);
};
Task.prototype.getViews = function(callback) {
    return taskCaller(this, 'getViews', [callback]);
};
Task.prototype.getMetaData = function(callback) {
    return taskCaller(this, 'getMetaData', [callback]);
};
Task.prototype.showViews = function(views, callback) {
    return taskCaller(this, 'showViews', [views, callback]);
};
Task.prototype.gradeTask = function(randomSeed, answer, minScore, maxScore) {
    return taskCaller(this, 'randomSeed', [randomSeed, answer, minScore, maxScore]);
};

/*
 * Platform object definition, created from a Task object (see below)
 */

function Platform(task) {
   this.task = task;
};
Platform.prototype.getTask = function() {
   return this.task;
};

/*
 * Simple prototypes for platform API functions, to be overriden by your
 * platform's specific functions (for each platform object)
 */

Platform.prototype.validate = function(mode) {};
Platform.prototype.showView = function(views) {};
Platform.prototype.askHint = function(platformToken) {};
Platform.prototype.updateHeight = function(height) {this.task.iframe.height(parseInt(height)+40);};
Platform.prototype.getTaskParams = function(key, defaultValue) {
   var res = {minScore: -3, maxScore: 6, randomSeed: 0, noScore: 0, readOnly: false};
   if (typeof key !== 'undefined') {
      if (key !== 'options' && key in res) {
         return this.taskParams[key];
      } else {
         return (typeof defaultValue !== 'undefined') ? defaultValue : null;
      }
   }
   return res;
}

Platform.prototype.openUrl = function(url) {
   // TODO
};
<commit_msg>add missing functions in TaskProxyManager, fix gradeTask<commit_after>'use strict';

/*
 * Same domain task proxy implementation for Bebras task API - v1.0 - 08/2014
 *
 * This file implements a "TaskProxyManager" object in the global scope so that
 * platforms using this file can just call
 * "myTask = TaskProxyManager.getTaskProxy(task_id)" to get the task object
 * associated to a task loaded in an iframe. Then they can just call
 * "myTask.myFunction()", with function of the API, the task object will take
 * care of message sending and receiving with the actual task.
 * 'task_id' here refers to the "id" attribute of the iframe in which a task is
 * loaded.
 *
 * It depends on jQuery.
 *
 * Currently, if you do not use the callback system, you have to make sure
 * iframe is ready before calling functions returning a result.
 * You can call task.iframe_loaded to know if it is or not.
 * This limitation doesn't apply if you use the callback system.
 *
 */

var TaskProxyManager = {
   tasks: {},
   platforms: {},
   getTaskProxy: function(idFrame, callback, force) {
      if (!force && TaskProxyManager.tasks[idFrame]) {
         callback(TaskProxyManager.tasks[idFrame]);
      } else {
         if (force) {
            TaskProxyManager.deleteTaskProxy(idFrame);
         }
         $('#'+idFrame).each(function() {
            var curTask = new Task($(this));
            TaskProxyManager.tasks[idFrame] = curTask;
         });
         callback(TaskProxyManager.tasks[idFrame]);
      }
   },
   setPlatform: function(task, platform) {
      TaskProxyManager.platforms[task.Id] = platform;
      TaskProxyManager.tasks[task.Id].setPlatform(platform);
   },
   deleteTaskProxy: function(idFrame) {
      delete(TaskProxyManager.tasks[idFrame]);
      delete(TaskProxyManager.platforms[idFrame]);
   },
   getUrl: function(taskUrl, sToken, sPlatform, prefix) {
      return taskUrl+'?sToken='+encodeURIComponent(sToken)+'&sPlatform='+encodeURIComponent(sPlatform);
   }
}

TaskProxyManager.getGraderProxy = TaskProxyManager.getTaskProxy;

var taskCaller = function(task, request, content) {
   // TODO: handle case where iframe_loaded is false and caller expects a result...
   if (!task.iframe_loaded) {
      setTimeout(function() {
         taskCaller(task, request, content);
      }, 250);
   } else {
      if (task.distantTask && typeof task.distantTask[request] === 'function') {
         if (typeof content === 'string' || (typeof content === 'object' && Object.prototype.toString.call(content) !== '[object Array]')) {
            content = [content];
         }
         var functionsToTrigger = {load: true, unload:true, reloadAnswer:true, showViews: true, reloadState: true};
         if (functionsToTrigger[request] != undefined) {
            var askedCallback = content[content.length - 1];
            if (typeof askedCallback === 'function') {
               var newCallback = function() {
                  task.distantPlatform.trigger(request, content);
                  askedCallback();
               };
               content[content.length -1] = newCallback;
            }
         }
         var res = task.distantTask[request].apply(task.distantTask, content);
         return res;
      }// else {
      //   console.error("Task "+task.Id+" doesn't implement "+request);
      //}
   }
}

/*
 * Task object, created from an iframe DOM element
 */
function Task(iframe) {
   this.iframe = iframe;
   this.iframe_loaded = false;
   this.distantTask = null;
   this.Id = iframe.attr('id')
   this.elementsLoaded = false;
   this.platform = null;
   this.setPlatform = function(platform) {
      this.platform = platform;
      if (this.iframe_loaded) {
         this.distantPlatform.setPlatform(platform);
      }
   }
   var that = this;
   // checking if task is already available
   if (that.iframe[0].contentWindow.task) {
      that.iframe_loaded = true;
      that.distantTask = that.iframe[0].contentWindow.task;
      that.distantPlatform = that.iframe[0].contentWindow.platform;
      if (that.platform != null) {
         that.distantPlatform.setPlatform(that.platform);
      }
   } else {
      this.iframe.load(function() {
         that.iframe_loaded = true;
         that.distantTask = that.iframe[0].contentWindow.task;
         that.distantPlatform = that.iframe[0].contentWindow.platform;
         if (that.platform != null) {
            that.distantPlatform.setPlatform(that.platform);
         }
      });
   }
}

Task.prototype.getSourceId = function() {
   return this.Id;
}

Task.prototype.getTargetUrl = function() {
   return this.iframe.attr('src');
}

Task.prototype.getTarget = function() {
   return this.iframe[0].contentWindow;
}

Task.prototype.getDomain = function() {
   var url = this.getTargetUrl();
   return url.substr(0, url.indexOf('/', 7));
}

/**
 * Task API functions
 */
Task.prototype.load = function(views, callback) {
    return taskCaller(this, 'load', [views, callback]);
};
Task.prototype.unload = function(callback) {
    return taskCaller(this, 'unload', [callback]);
};
Task.prototype.getHeight = function(callback) {
    return taskCaller(this, 'getHeight', [callback]);
};
Task.prototype.updateToken = function(token, callback) {
    return taskCaller(this, 'updateToken', [token, callback]);
};
Task.prototype.getAnswer = function(callback) {
    return taskCaller(this, 'getAnswer', [callback]);
};
Task.prototype.reloadAnswer = function(answer, callback) {
    return taskCaller(this, 'reloadAnswer', [answer, callback]);
};
Task.prototype.getState = function(callback) {
    return taskCaller(this, 'getState', [callback]);
};
Task.prototype.reloadState = function(state, callback) {
    return taskCaller(this, 'reloadState', [state, callback]);
};
Task.prototype.displayMessage = function(type, html, isOptional) {
    return taskCaller(this, 'displayMessage', [type, html, isOptional]);
};
Task.prototype.getViews = function(callback) {
    return taskCaller(this, 'getViews', [callback]);
};
Task.prototype.getMetaData = function(callback) {
    return taskCaller(this, 'getMetaData', [callback]);
};
Task.prototype.showViews = function(views, callback) {
    return taskCaller(this, 'showViews', [views, callback]);
};
Task.prototype.gradeAnswer = function(randomSeed, answer, minScore, maxScore) {
    return taskCaller(this, 'gradeAnswer', [randomSeed, answer, minScore, maxScore]);
};
// for grader.gradeTask
Task.prototype.gradeTask = function(randomSeed, answer, minScore, maxScore) {
    return taskCaller(this, 'gradeAnswer', [randomSeed, answer, minScore, maxScore]);
};

/*
 * Platform object definition, created from a Task object (see below)
 */

function Platform(task) {
   this.task = task;
};
Platform.prototype.getTask = function() {
   return this.task;
};

/*
 * Simple prototypes for platform API functions, to be overriden by your
 * platform's specific functions (for each platform object)
 */

Platform.prototype.validate = function(mode) {};
Platform.prototype.showView = function(views) {};
Platform.prototype.askHint = function(platformToken) {};
Platform.prototype.updateHeight = function(height) {this.task.iframe.height(parseInt(height)+40);};
Platform.prototype.getTaskParams = function(key, defaultValue) {
   var res = {minScore: -3, maxScore: 6, randomSeed: 0, noScore: 0, readOnly: false};
   if (typeof key !== 'undefined') {
      if (key !== 'options' && key in res) {
         return this.taskParams[key];
      } else {
         return (typeof defaultValue !== 'undefined') ? defaultValue : null;
      }
   }
   return res;
}

Platform.prototype.openUrl = function(url) {
   // TODO
};
<|endoftext|>