<commit_before>/**
 * Pulse.js - Beats per minute (BPM) automatic detection with Web Audio API.
 * @param  {object} global - The window object
 * @return {void}
 */
(function(global) {

    "use strict";

    var self,

    /**
     * Pulse constructor
     * @constructor
     */
    Pulse = function() {

        self = this;

        this.audioContext = this.getAudioContext();
        this.buffer = null;

        this.WEB_AUDIO_API_NOT_SUPPORTED = 1001;
        this.REQUEST_PROGRESS = 101;
        this.REQUEST_LOAD = 102;
        this.REQUEST_ERROR = 1102;
        this.REQUEST_ABORT = 104;

        var notifier = Object.getNotifier(this);

        Object.defineProperty(this, 'status', {
            get: function() {
                return parseInt(status, 10);
            },
            set: function(s) {
                console.log(s, status);
                if (s == status) {
                    console.log('same')
                    return;
                }

                notifier.notify({
                    type: 'update',
                    name: 'status'
                });

                status = s;
            }
        });

        Object.observe(this, function(changes) {
            console.log(self.status)
            switch(self.status) {
                case self.WEB_AUDIO_API_NOT_SUPPORTED :
                    console.error("WEB_AUDIO_API_NOT_SUPPORTED");
                break;

                case self.REQUEST_PROGRESS :
                    console.log("REQUEST_PROGRESS");
                break;

                case self.REQUEST_LOAD :
                    console.log("REQUEST_LOAD");
                break;

                case self.REQUEST_ERROR :
                    console.error("REQUEST_ERROR");
                break;

                case self.REQUEST_ABORT :
                    console.error("REQUEST_ABORT");
                break;

                default :
                    console.error("STATUS NOT IMPLEMENTED");
                break;
            }
        });
    };

    /**
     * getAudioContext description
     * @return {object} AudioContext
     */
    Pulse.prototype.getAudioContext = function() {
        try {
            global.AudioContext = global.AudioContext || global.webkitAudioContext;
            return new global.AudioContext();
        } catch(e) {
            this.status = this.WEB_AUDIO_API_NOT_SUPPORTED;
            return null;
        }
    };

    /**
     * LoadBufferFromURI description
     * @param  {string} uri - The URI of the song
     * @param  {string} options - Options
     * @return {object}
     */
    Pulse.prototype.loadBufferFromURI = function(uri, options) {

        if(this.audioContext === null) {
            return false;
        }

        options.onsuccess = options.onsuccess || function() {};
        options.onerror = options.onerror || function() {};

        var request = new XMLHttpRequest();

        request.open("GET", uri, true);
        request.responseType = "arraybuffer";

        request.addEventListener("progress", function(event) {
            self._requestProgress(event);
        }, false);

        request.addEventListener("load", function(event) {
            self._requestLoad(event, this, options);
        }, false);

        request.addEventListener("error", function() {
            self._requestError(event, this, options);
        }, false);

        request.addEventListener("abort", function() {
            self._requestAbort();
        }, false);

        request.send(null);

        return true;
    };

    Pulse.prototype._requestProgress = function(event) {
        this.status = this.REQUEST_PROGRESS;

        if(event.lengthComputable) {
            //console.log(event.loaded + ' / ' + event.total);
        }
    };

    Pulse.prototype._requestLoad = function(event, request, options) {
        console.log(request);
        if(request.readyState === request.DONE) {
            this.buffer = request.response;
            this.status = this.REQUEST_LOAD;
            options.onsuccess(this);
        } else {
            this._requestError(event, request, options);
        }
    };

    Pulse.prototype._requestError = function(event, request, options) {
        this.status = this.REQUEST_ERROR;
        options.onerror(request);
    };

    Pulse.prototype._requestAbort = function() {
        this.status = this.REQUEST_ABORT;
    };

    Pulse.prototype.process = function() {
        this.audioContext.decodeAudioData(
            this.buffer,
            this.processBpm,
            function(error) {
                self.status = self.DECODING_ERROR;
            }
        );
    };

    Pulse.prototype.getOfflineContext = function(buffer) {
        var offlineContext = new global.OfflineAudioContext(1, buffer.length, buffer.sampleRate),
            source = offlineContext.createBufferSource(),
            filter = offlineContext.createBiquadFilter();

        source.buffer = buffer;
        filter.type = "lowpass";

        source.connect(filter);
        filter.connect(offlineContext.destination);

        source.start(0);
        return offlineContext;
    };

    Pulse.prototype.processBpm = function(buffer) {

        var offlineContext = self.getOfflineContext(buffer);

        offlineContext.oncomplete = function(event) {
            self.peaks = self.getPeaks(event);
        };

        offlineContext.startRendering();
    };

    Pulse.prototype.getChannelDataMinMax = function(channelData) {
        var length = channelData.length,
            min = channelData[0],
            max = channelData[0],
            j;

        for(j = 1; j < length; j++) {
            min = Math.min(min, channelData[j]);
            max = Math.max(max, channelData[j]);
        }

        return {
            min: min,
            max: max
        };
    };

    Pulse.prototype.getPeaks = function(event) {

        var channelData = event.renderedBuffer.getChannelData(0),
            limit = this.getChannelDataMinMax(channelData),
            intervalMin = 230, // ms, max tempo = 260 bpm
            amplitude = Math.abs(limit.min) + Math.abs(limit.max),
            maxThreshold = limit.min + amplitude * 0.9, // 90% uppest beats
            minThreshold = limit.min + amplitude * 0.3, // 30% uppest beats
            threshold = maxThreshold,
            acuracy = event.renderedBuffer.sampleRate * (intervalMin / 1000),
            peakFilter = [];

        // grab peaks
        while (threshold >= minThreshold && peakFilter.length <= parseInt(this.buffer.duration, 10)) {
            j = 0;
            for(; j < length; j++) {
                if (channelData[j] > threshold) {
                    peakFilter.push(j);

                    j += acuracy;
                }
            }
            threshold -= 0.05; // -5% every interation
        }

        peakFilter.sort(function(a, b) {
            return a - b;
        });

        return peakFilter;
    };

    /*Pulse.prototype.get = function() {
        this.audioContext.decodeAudioData(
            response,
            this.processBpm,
            function(error) {
                alert("Decoding error:" + error);
            }
        );
    };

    Pulse.prototype.getPeaks = function() {
        return this._peaks;
    };

    Pulse.prototype.getSimulatedPeaks = function() {
        return this._peaks;
    };

    Pulse.prototype.getPulseTempo44 = function() {
        return this._peaks;
    };*/

    /*
    Pulse.prototype.processBpm = function(buffer) {
        if(!global.fingr.checkAudioBuffer(buffer)) {
            global.alert("Nope...");
            return false;
        }

        var self = this;

        var offlineContext = new global.OfflineAudioContext(1, buffer.length, buffer.sampleRate);

        var source = offlineContext.createBufferSource();
        source.buffer = buffer;

        var filter = offlineContext.createBiquadFilter();
        filter.type = "lowpass";

        source.connect(filter);
        filter.connect(offlineContext.destination);

        source.start(0);
        offlineContext.startRendering();

        var playbackTempo = [];

        offlineContext.oncomplete = function(e) {
            var filteredBuffer = e.renderedBuffer;

            var data = filteredBuffer.getChannelData(0);
            var length = data.length;

            var min = data[0], max = data[0];
            for(var j = 1; j < length; j++) {
                min = Math.min(min, data[j]);
                max = Math.max(max, data[j]);
            }

            var intervalMin = 230; // ms, max tempo = 260 bpm

            var amplitude = Math.abs(min) + Math.abs(max);
            var maxThreshold = min + amplitude * 0.9; // 90% uppest beats
            var threshold = maxThreshold;
            var minThreshold = min + amplitude * 0.3; // 30% uppest beats

            var acuracy = filteredBuffer.sampleRate * (intervalMin / 1000);
            var peakFilter = [];
            var hiBeats = [];

            // grab peaks
            while (threshold >= minThreshold && peakFilter.length <= parseInt(buffer.duration, 10)) {
                j = 0;
                for(; j < length; j++) {
                    if (data[j] > threshold) {
                        peakFilter.push(j);

                        j += acuracy;
                    }
                }
                threshold -= 0.05; // -5% every interation
            }

            // sort peaks desc
            peakFilter.sort(function(a, b) {
                return a - b;
            });

            // convert from hz to to milliseconds
            for (var i in peakFilter) {
                peakFilter[i] = Math.floor((peakFilter[i] / filteredBuffer.sampleRate) * 1000);
            }

            // remove all duplicates and 0 values
            peakFilter = peakFilter.filter(function(item, pos) {
                return (!pos || item  > peakFilter[pos - 1]) && item > 0;
            });

            // count interval durations between each peak
            var intervals = {};
            for (var i = 1; i < peakFilter.length; i++) {
                for (var j = 0; j < i; j++) {

                    // assuming intervals must be less than 260 bpm (more than ~230 ms)
                    if (peakFilter[i] - peakFilter[j] >= 230) {
                        if (intervals[peakFilter[i] - peakFilter[j]] === undefined) {
                            intervals[peakFilter[i] - peakFilter[j]] = 0;
                        }
                        intervals[peakFilter[i] - peakFilter[j]]++;
                    }
                }
            }

            // quadratic mean to compute the average power
            var square = 0;
            var count = 0;
            for (var i in intervals) {
                square += Math.pow(intervals[i], 2);
                count++;
            }

            var avgCountInterval = Math.sqrt(square / count);

            // get max beats between an interval (1000 ms)
            var max = 0
            var ms = 0;
            var msBetween = [];
            var k;
            for (var i in intervals) {
                if (intervals[i] > avgCountInterval) {
                    if (intervals[i] > max) {
                        max = intervals[i];
                        ms = parseInt(i, 10);
                    }

                    k = Math.floor(i / 500); // segmentation by 500, this needs to be computed
                    if (msBetween[k] === undefined) {
                        msBetween.push({ max: 0, ms: parseInt(i, 10) });
                    }

                    if (msBetween[k] !== undefined && intervals[i] > msBetween[k].max) {
                        msBetween[k] = { max: intervals[i], ms: parseInt(i, 10) };
                    }
                }
            }

            // compare ms with all other time beats
            var referenceMs = msBetween.slice(0, 3);
            var sumMargins = [];

            for (var i = 0; i < referenceMs.length; i++) {
                sumMargins.push(0);
                for (var j in msBetween) {
                    sumMargins[i] += msBetween[j].ms % referenceMs[i].ms;
                }
            }

            var minMarginIndex = 0;
            var minMargin = sumMargins[minMarginIndex];
            for (var i = 1; i < sumMargins.length; i++) {
                if (minMargin > sumMargins[i]) {
                    minMargin = sumMargins[i];
                    minMarginIndex = i;
                }
            }

            console.log("ms = " + referenceMs[minMarginIndex].ms);
            console.log("tempo = " + 60000 / referenceMs[minMarginIndex].ms);

            // find the start beat of tempo
            var tempo = Math.round(60000 / referenceMs[minMarginIndex].ms);
            var tempoMs = referenceMs[minMarginIndex].ms;

            document.getElementById("pulse").style.webkitAnimationDuration = tempoMs + "ms";
            document.getElementById("pulse").innerHTML = tempo;

            for (var i = 0; i < peakFilter.length; i++) {
                for (var j = 0; j < i; j++) {
                    if(peakFilter[i] - peakFilter[j] == tempoMs) {
                       playbackTempo.push(peakFilter[i]);
                       playbackTempo.push(peakFilter[j]);
                    }
                }
            }

            playbackTempo.sort(function(a, b) {
                return a - b;
            });

            playbackTempo = playbackTempo.filter(function(item, pos) {
                return (!pos || item > playbackTempo[pos - 1] + 230) && item > 0;
            });

            // detect chained
            var chainedKeys = [];
            var chainedKeysMax = [];
            var errorMs = 2;
            for (var i = 0; i < playbackTempo.length - 1; i++) {
                if (playbackTempo[i] + tempoMs <= playbackTempo[i + 1] + errorMs && playbackTempo[i] + tempoMs >= playbackTempo[i + 1] - errorMs) {
                    chainedKeys.push(i);
                } else {
                    if (chainedKeysMax.length < chainedKeys.length) {
                        chainedKeysMax = chainedKeys;
                    }

                    chainedKeys = [];
                }
            }

            var simulatedBeats = [];

            //console.log(playbackTempo);
            //console.log(chainedKeysMax);
            //console.log(peakFilter);

            if (chainedKeysMax.length) {
                var minChained = playbackTempo[chainedKeysMax[0]];
                while (minChained > tempoMs) {
                    minChained -= tempoMs;
                }

                var maxChained = playbackTempo[chainedKeysMax.length - 1];
                while (maxChained < buffer.duration * 1000) {
                    maxChained += tempoMs;
                }

                for (var i = minChained; i <= maxChained; i += tempoMs) {
                    simulatedBeats.push(i);
                };
            }

            // to sec
            for (var i = 0; i < simulatedBeats.length; i++) {
                simulatedBeats[i] -= global.fingr.msMoveDown;
                simulatedBeats[i] /= 1000;
            }

            // to sec
            for (var i = 0; i < peakFilter.length; i++) {
                peakFilter[i] -= global.fingr.msMoveDown;
                peakFilter[i] /= 1000;
            }

            console.log(peakFilter);
            console.log(simulatedBeats);

            global.fingr.beatPerMS = tempoMs;

            global.fingr.dance(buffer, peakFilter, simulatedBeats);
        };
    };
    */

    global.Pulse = Pulse;

}(window));<commit_msg>Fix peaks computation<commit_after>/**
 * Pulse.js - Beats per minute (BPM) automatic detection with Web Audio API.
 * @param  {object} global - The window object
 * @return {void}
 */
(function(global) {

    "use strict";

    var self,

    /**
     * Pulse constructor
     * @constructor
     */
    Pulse = function() {

        self = this;

        this.audioContext = this.getAudioContext();
        this.buffer = null;

        this.WEB_AUDIO_API_NOT_SUPPORTED = 1001;
        this.REQUEST_PROGRESS = 101;
        this.REQUEST_LOAD = 102;
        this.REQUEST_ERROR = 1102;
        this.REQUEST_ABORT = 104;

        var notifier = Object.getNotifier(this);

        Object.defineProperty(this, 'status', {
            get: function() {
                return parseInt(status, 10);
            },
            set: function(s) {
                if (s == status) {
                    return;
                }

                notifier.notify({
                    type: 'update',
                    name: 'status',
                    oldValue: s
                });

                status = s;
            }
        });

        Object.observe(this, function(changes) {
            for(var i = 0; i < changes.length; i++) {
                if(
                    changes[i].type === 'update' &&
                    changes[i].name === 'status' &&
                    changes[i].lastValue != self.status
                ) {
                    switch(self.status) {
                        case self.WEB_AUDIO_API_NOT_SUPPORTED :
                            console.error("WEB_AUDIO_API_NOT_SUPPORTED");
                        break;

                        case self.REQUEST_PROGRESS :
                            console.log("REQUEST_PROGRESS");
                        break;

                        case self.REQUEST_LOAD :
                            console.log("REQUEST_LOAD");
                        break;

                        case self.REQUEST_ERROR :
                            console.error("REQUEST_ERROR");
                        break;

                        case self.REQUEST_ABORT :
                            console.error("REQUEST_ABORT");
                        break;

                        default :
                            console.error("STATUS NOT IMPLEMENTED");
                        break;
                    }
                }
            }
        });
    };

    /**
     * getAudioContext description
     * @return {object} AudioContext
     */
    Pulse.prototype.getAudioContext = function() {
        try {
            global.AudioContext = global.AudioContext || global.webkitAudioContext;
            return new global.AudioContext();
        } catch(e) {
            this.status = this.WEB_AUDIO_API_NOT_SUPPORTED;
            return null;
        }
    };

    /**
     * LoadBufferFromURI description
     * @param  {string} uri - The URI of the song
     * @param  {string} options - Options
     * @return {object}
     */
    Pulse.prototype.loadBufferFromURI = function(uri, options) {

        if(this.audioContext === null) {
            return false;
        }

        options.onsuccess = options.onsuccess || function() {};
        options.onerror = options.onerror || function() {};

        var request = new XMLHttpRequest();

        request.open("GET", uri, true);
        request.responseType = "arraybuffer";

        request.addEventListener("progress", function(event) {
            self._requestProgress(event);
        }, false);

        request.addEventListener("load", function(event) {
            self._requestLoad(event, this, options);
        }, false);

        request.addEventListener("error", function() {
            self._requestError(event, this, options);
        }, false);

        request.addEventListener("abort", function() {
            self._requestAbort();
        }, false);

        request.send(null);

        return true;
    };

    Pulse.prototype._requestProgress = function(event) {
        this.status = this.REQUEST_PROGRESS;

        if(event.lengthComputable) {
            //console.log(event.loaded + ' / ' + event.total);
        }
    };

    Pulse.prototype._requestLoad = function(event, request, options) {
        if(request.readyState === request.DONE) {
            this.buffer = request.response;
            this.status = this.REQUEST_LOAD;
            options.onsuccess(this);
        } else {
            this._requestError(event, request, options);
        }
    };

    Pulse.prototype._requestError = function(event, request, options) {
        this.status = this.REQUEST_ERROR;
        options.onerror(request);
    };

    Pulse.prototype._requestAbort = function() {
        this.status = this.REQUEST_ABORT;
    };

    Pulse.prototype.process = function() {
        this.audioContext.decodeAudioData(
            this.buffer,
            this.processBpm,
            function(error) {
                self.status = self.DECODING_ERROR;
            }
        );
    };

    Pulse.prototype.getOfflineContext = function(buffer) {
        var offlineContext = new global.OfflineAudioContext(1, buffer.length, buffer.sampleRate),
            source = offlineContext.createBufferSource(),
            filter = offlineContext.createBiquadFilter();

        source.buffer = buffer;
        filter.type = "lowpass";

        source.connect(filter);
        filter.connect(offlineContext.destination);

        source.start(0);
        return offlineContext;
    };

    Pulse.prototype.processBpm = function(buffer) {

        var offlineContext = self.getOfflineContext(buffer);

        offlineContext.oncomplete = function(event) {
            self.peaks = self.getPeaks(event);
            console.log(self.peaks);
        };

        offlineContext.startRendering();
    };

    Pulse.prototype.getChannelDataMinMax = function(channelData) {
        var length = channelData.length,
            min = channelData[0],
            max = channelData[0],
            j;

        for(j = 1; j < length; j++) {
            min = Math.min(min, channelData[j]);
            max = Math.max(max, channelData[j]);
        }

        return {
            min: min,
            max: max
        };
    };

    Pulse.prototype.getPeaks = function(event) {

        var channelData = event.renderedBuffer.getChannelData(0),
            limit = this.getChannelDataMinMax(channelData),
            intervalMin = 230, // ms, max tempo = 260 bpm
            amplitude = Math.abs(limit.min) + Math.abs(limit.max),
            maxThreshold = limit.min + amplitude * 0.9, // 90% uppest beats
            minThreshold = limit.min + amplitude * 0.3, // 30% uppest beats
            threshold = maxThreshold,
            acuracy = event.renderedBuffer.sampleRate * (intervalMin / 1000),
            peakFilter = [],
            duration = parseInt(event.renderedBuffer.duration, 10),
            length = channelData.length,
            j;

        // grab peaks
        while (
            threshold >= minThreshold &&
            peakFilter.length <= duration
        ) {
            j = 0;
            for(; j < length; j++) {
                if (channelData[j] > threshold) {
                    peakFilter.push(j);

                    j += acuracy;
                }
            }
            threshold -= 0.05; // -5% every interation
        }

        peakFilter.sort(function(a, b) {
            return a - b;
        });

        return peakFilter;
    };

    /*Pulse.prototype.get = function() {
        this.audioContext.decodeAudioData(
            response,
            this.processBpm,
            function(error) {
                alert("Decoding error:" + error);
            }
        );
    };

    Pulse.prototype.getPeaks = function() {
        return this._peaks;
    };

    Pulse.prototype.getSimulatedPeaks = function() {
        return this._peaks;
    };

    Pulse.prototype.getPulseTempo44 = function() {
        return this._peaks;
    };*/

    /*
    Pulse.prototype.processBpm = function(buffer) {
        if(!global.fingr.checkAudioBuffer(buffer)) {
            global.alert("Nope...");
            return false;
        }

        var self = this;

        var offlineContext = new global.OfflineAudioContext(1, buffer.length, buffer.sampleRate);

        var source = offlineContext.createBufferSource();
        source.buffer = buffer;

        var filter = offlineContext.createBiquadFilter();
        filter.type = "lowpass";

        source.connect(filter);
        filter.connect(offlineContext.destination);

        source.start(0);
        offlineContext.startRendering();

        var playbackTempo = [];

        offlineContext.oncomplete = function(e) {
            var filteredBuffer = e.renderedBuffer;

            var data = filteredBuffer.getChannelData(0);
            var length = data.length;

            var min = data[0], max = data[0];
            for(var j = 1; j < length; j++) {
                min = Math.min(min, data[j]);
                max = Math.max(max, data[j]);
            }

            var intervalMin = 230; // ms, max tempo = 260 bpm

            var amplitude = Math.abs(min) + Math.abs(max);
            var maxThreshold = min + amplitude * 0.9; // 90% uppest beats
            var threshold = maxThreshold;
            var minThreshold = min + amplitude * 0.3; // 30% uppest beats

            var acuracy = filteredBuffer.sampleRate * (intervalMin / 1000);
            var peakFilter = [];
            var hiBeats = [];

            // grab peaks
            while (threshold >= minThreshold && peakFilter.length <= parseInt(buffer.duration, 10)) {
                j = 0;
                for(; j < length; j++) {
                    if (data[j] > threshold) {
                        peakFilter.push(j);

                        j += acuracy;
                    }
                }
                threshold -= 0.05; // -5% every interation
            }

            // sort peaks desc
            peakFilter.sort(function(a, b) {
                return a - b;
            });

            // convert from hz to to milliseconds
            for (var i in peakFilter) {
                peakFilter[i] = Math.floor((peakFilter[i] / filteredBuffer.sampleRate) * 1000);
            }

            // remove all duplicates and 0 values
            peakFilter = peakFilter.filter(function(item, pos) {
                return (!pos || item  > peakFilter[pos - 1]) && item > 0;
            });

            // count interval durations between each peak
            var intervals = {};
            for (var i = 1; i < peakFilter.length; i++) {
                for (var j = 0; j < i; j++) {

                    // assuming intervals must be less than 260 bpm (more than ~230 ms)
                    if (peakFilter[i] - peakFilter[j] >= 230) {
                        if (intervals[peakFilter[i] - peakFilter[j]] === undefined) {
                            intervals[peakFilter[i] - peakFilter[j]] = 0;
                        }
                        intervals[peakFilter[i] - peakFilter[j]]++;
                    }
                }
            }

            // quadratic mean to compute the average power
            var square = 0;
            var count = 0;
            for (var i in intervals) {
                square += Math.pow(intervals[i], 2);
                count++;
            }

            var avgCountInterval = Math.sqrt(square / count);

            // get max beats between an interval (1000 ms)
            var max = 0
            var ms = 0;
            var msBetween = [];
            var k;
            for (var i in intervals) {
                if (intervals[i] > avgCountInterval) {
                    if (intervals[i] > max) {
                        max = intervals[i];
                        ms = parseInt(i, 10);
                    }

                    k = Math.floor(i / 500); // segmentation by 500, this needs to be computed
                    if (msBetween[k] === undefined) {
                        msBetween.push({ max: 0, ms: parseInt(i, 10) });
                    }

                    if (msBetween[k] !== undefined && intervals[i] > msBetween[k].max) {
                        msBetween[k] = { max: intervals[i], ms: parseInt(i, 10) };
                    }
                }
            }

            // compare ms with all other time beats
            var referenceMs = msBetween.slice(0, 3);
            var sumMargins = [];

            for (var i = 0; i < referenceMs.length; i++) {
                sumMargins.push(0);
                for (var j in msBetween) {
                    sumMargins[i] += msBetween[j].ms % referenceMs[i].ms;
                }
            }

            var minMarginIndex = 0;
            var minMargin = sumMargins[minMarginIndex];
            for (var i = 1; i < sumMargins.length; i++) {
                if (minMargin > sumMargins[i]) {
                    minMargin = sumMargins[i];
                    minMarginIndex = i;
                }
            }

            console.log("ms = " + referenceMs[minMarginIndex].ms);
            console.log("tempo = " + 60000 / referenceMs[minMarginIndex].ms);

            // find the start beat of tempo
            var tempo = Math.round(60000 / referenceMs[minMarginIndex].ms);
            var tempoMs = referenceMs[minMarginIndex].ms;

            document.getElementById("pulse").style.webkitAnimationDuration = tempoMs + "ms";
            document.getElementById("pulse").innerHTML = tempo;

            for (var i = 0; i < peakFilter.length; i++) {
                for (var j = 0; j < i; j++) {
                    if(peakFilter[i] - peakFilter[j] == tempoMs) {
                       playbackTempo.push(peakFilter[i]);
                       playbackTempo.push(peakFilter[j]);
                    }
                }
            }

            playbackTempo.sort(function(a, b) {
                return a - b;
            });

            playbackTempo = playbackTempo.filter(function(item, pos) {
                return (!pos || item > playbackTempo[pos - 1] + 230) && item > 0;
            });

            // detect chained
            var chainedKeys = [];
            var chainedKeysMax = [];
            var errorMs = 2;
            for (var i = 0; i < playbackTempo.length - 1; i++) {
                if (playbackTempo[i] + tempoMs <= playbackTempo[i + 1] + errorMs && playbackTempo[i] + tempoMs >= playbackTempo[i + 1] - errorMs) {
                    chainedKeys.push(i);
                } else {
                    if (chainedKeysMax.length < chainedKeys.length) {
                        chainedKeysMax = chainedKeys;
                    }

                    chainedKeys = [];
                }
            }

            var simulatedBeats = [];

            //console.log(playbackTempo);
            //console.log(chainedKeysMax);
            //console.log(peakFilter);

            if (chainedKeysMax.length) {
                var minChained = playbackTempo[chainedKeysMax[0]];
                while (minChained > tempoMs) {
                    minChained -= tempoMs;
                }

                var maxChained = playbackTempo[chainedKeysMax.length - 1];
                while (maxChained < buffer.duration * 1000) {
                    maxChained += tempoMs;
                }

                for (var i = minChained; i <= maxChained; i += tempoMs) {
                    simulatedBeats.push(i);
                };
            }

            // to sec
            for (var i = 0; i < simulatedBeats.length; i++) {
                simulatedBeats[i] -= global.fingr.msMoveDown;
                simulatedBeats[i] /= 1000;
            }

            // to sec
            for (var i = 0; i < peakFilter.length; i++) {
                peakFilter[i] -= global.fingr.msMoveDown;
                peakFilter[i] /= 1000;
            }

            console.log(peakFilter);
            console.log(simulatedBeats);

            global.fingr.beatPerMS = tempoMs;

            global.fingr.dance(buffer, peakFilter, simulatedBeats);
        };
    };
    */

    global.Pulse = Pulse;

}(window));<|endoftext|>


<commit_before>'use strict'

var Promise = require('q');
var _ = require('./lodash')

module.exports = function(service) {
  var reductiofy = require('./reductiofy')(service)
  var filters = require('./filters')(service)
  var postAggregation = require('./postAggregation')(service)

  return function doQuery(queryObj) {

    var query
    var queryHash = JSON.stringify(queryObj)

    // Attempt to reuse an exact copy of this query that is present elsewhere
    for (var i = 0; i < service.columns.length; i++) {
      for (var j = 0; j < service.columns[i].queries.length; j++) {
        if (service.columns[i].queries[j].hash === queryHash) {
          return Promise.try(function() {
            return service.columns[i].queries[j]
          })
        }
      }
    }

    query = {
      // The Universe for continuous promise chaining
      universe: service,
      // Parent column
      column: null,
      // Crossfilter instances
      crossfilter: service.cf,
      dimension: null,
      group: null,
      // Reductio reducer instance
      reducer: null,
      // Listeners
      removeListeners: [],
      // Post aggregations
      postAggregations: [],
      // Original query passed in to query method
      original: queryObj,
      hash: queryHash
    }

    // Default queryObj
    if (_.isUndefined(query.original)) {
      query.original = {}
    }
    // Default select
    if (_.isUndefined(query.original.select)) {
      query.original.select = {
        $count: true
      }
    }
    // Default to groupAll
    query.original.groupBy = query.original.groupBy || true

    // Attach the query api to the query object
    addQueryMethods(query)

    return createColumn(query)
      .then(makeCrossfilterGroup)
      .then(buildRequiredColumns)
      .then(applyQuery)

    function createColumn(query) {
      // Ensure column is created
      return service.column({
          key: query.original.groupBy,
          type: !_.isUndefined(query.type) ? query.type : null,
          array: !!query.array
        })
        .then(function() {
          // Attach the column to the query
          var column = service.column.find(query.original.groupBy)
          query.column = column
          column.queries.push(query)
          return query
        })
    }

    function makeCrossfilterGroup(query) {
      // Create the grouping on the columns dimension
      // Using Promise Resolve allows support for crossfilter async
      // TODO check if query already exists, and use the same base query // if possible
      return Promise.resolve(query.column.dimension.group())
        .then(function(g) {
          query.group = g
          return query
        })
    }

    function buildRequiredColumns(query) {
      var requiredColumns = filters.scanForDynamicFilters(query.original)
        // We need to scan the group for any filters that would require
        // the group to be rebuilt when data is added or removed in any way.
      if (requiredColumns.length) {
        return Promise.all(_.map(requiredColumns, function(columnKey) {
            return service.column({
              key: columnKey,
              dynamicReference: query.group
            })
          }))
          .then(function() {
            // Here, we create a listener to recreate and apply the reducer
            // (with updated reference data) to
            // the group anytime data changes
            var stopListeningForData = service.onDataChange(function applyReducerOnDataChange() {
              return applyQuery(query)
            })
            query.column.removeListeners.push(stopListeningForData)
            return query
          })
      }
      return query
    }

    function applyQuery(query) {
      // Create the reducer using reductio and the Universe
      // Query Syntax
      return buildReducer(query)
        .then(applyReducer)
        .then(attachData)
    }

    function buildReducer(query) {
      return reductiofy(query.original)
        .then(function(reducer) {
          query.reducer = reducer
          return query
        })
    }

    function applyReducer(query) {
      return Promise.resolve(query.reducer(query.group))
        .then(function() {
          return query
        })
    }

    function attachData(query) {
      return Promise.resolve(query.group.all())
        .then(function(data) {
          query.data = data
          return query
        })
    }

    function applyPostAggregations(query) {
      return query
    }

    function addQueryMethods(q) {
      _.assign(q, postAggregation.getMethods(q), {
        clear: clearQuery
      })

      function clearQuery() {
        _.forEach(q.removeListners, function(l) {
          l()
        })
        return Promise.try(function() {
            return q.group.dispose()
          })
          .then(function() {
            q.column.queries.splice(q.column.queries.indexOf(q), 1)
            if (!q.column.queries.length) {
              return service.clear(q.column.key)
            }
          })
          .then(function() {
            return service
          })
      }

    }
  }
}
<commit_msg>Scaffolding for post-aggregations<commit_after>'use strict'

var Promise = require('q');
var _ = require('./lodash')

module.exports = function(service) {
  var reductiofy = require('./reductiofy')(service)
  var filters = require('./filters')(service)
  var postAggregation = require('./postAggregation')(service)

  return function doQuery(queryObj) {

    var query
    var queryHash = JSON.stringify(queryObj)

    // Attempt to reuse an exact copy of this query that is present elsewhere
    for (var i = 0; i < service.columns.length; i++) {
      for (var j = 0; j < service.columns[i].queries.length; j++) {
        if (service.columns[i].queries[j].hash === queryHash) {
          return Promise.try(function() {
            return service.columns[i].queries[j]
          })
        }
      }
    }

    query = {
      // The Universe for continuous promise chaining
      universe: service,
      // Parent column
      column: null,
      // Crossfilter instances
      crossfilter: service.cf,
      dimension: null,
      group: null,
      // Reductio reducer instance
      reducer: null,
      // Listeners
      removeListeners: [],
      // Post aggregations
      postAggregations: [],
      // Original query passed in to query method
      original: queryObj,
      hash: queryHash
    }

    // Default queryObj
    if (_.isUndefined(query.original)) {
      query.original = {}
    }
    // Default select
    if (_.isUndefined(query.original.select)) {
      query.original.select = {
        $count: true
      }
    }
    // Default to groupAll
    query.original.groupBy = query.original.groupBy || true

    // Attach the query api to the query object
    addQueryMethods(query)

    return createColumn(query)
      .then(makeCrossfilterGroup)
      .then(buildRequiredColumns)
      .then(applyQuery)

    function createColumn(query) {
      // Ensure column is created
      return service.column({
          key: query.original.groupBy,
          type: !_.isUndefined(query.type) ? query.type : null,
          array: !!query.array
        })
        .then(function() {
          // Attach the column to the query
          var column = service.column.find(query.original.groupBy)
          query.column = column
          column.queries.push(query)
          column.removeListeners.push(function() {
            return query.clear()
          })
          return query
        })
    }

    function makeCrossfilterGroup(query) {
      // Create the grouping on the columns dimension
      // Using Promise Resolve allows support for crossfilter async
      // TODO check if query already exists, and use the same base query // if possible
      return Promise.resolve(query.column.dimension.group())
        .then(function(g) {
          query.group = g
          return query
        })
    }

    function buildRequiredColumns(query) {
      var requiredColumns = filters.scanForDynamicFilters(query.original)
        // We need to scan the group for any filters that would require
        // the group to be rebuilt when data is added or removed in any way.
      if (requiredColumns.length) {
        return Promise.all(_.map(requiredColumns, function(columnKey) {
            return service.column({
              key: columnKey,
              dynamicReference: query.group
            })
          }))
          .then(function() {
            // Here, we create a listener to recreate and apply the reducer
            // (with updated reference data) to
            // the group anytime data changes
            var stopDataListen = service.onDataChange(function() {
              return applyQuery(query)
            })
            query.removeListeners.push(stopDataListen)
            return query
          })
      }
      return query
    }

    function applyQuery(query) {

      // apply a one time listener for filtering. This is what allows
      // us to post aggregate and change the data on each filter
      var stopFilterListen = service.onFilter(function() {
        return attachData(query)
          .then(postAggregate)
      })
      query.removeListeners.push(stopFilterListen)

      return buildReducer(query)
        .then(applyReducer)
        .then(attachData)
        .then(postAggregate)
    }

    function buildReducer(query) {
      return reductiofy(query.original)
        .then(function(reducer) {
          query.reducer = reducer
          return query
        })
    }

    function applyReducer(query) {
      return Promise.resolve(query.reducer(query.group))
        .then(function() {
          return query
        })
    }

    function attachData(query) {
      return Promise.resolve(query.group.all())
        .then(function(data) {
          query.rawData = data
          return query
        })
    }

    function postAggregate(query) {
      // Here we slice/copy the data so we can post aggregate
      // and not skrew up crossfilter or reductio's innards
      query.data = query.rawData.slice()
      return query
    }

    function addQueryMethods(q) {
      _.assign(q, postAggregation.getMethods(q), {
        clear: clearQuery
      })

      function clearQuery() {
        _.forEach(q.removeListners, function(l) {
          l()
        })
        return Promise.try(function() {
            return q.group.dispose()
          })
          .then(function() {
            q.column.queries.splice(q.column.queries.indexOf(q), 1)
            if (!q.column.queries.length) {
              return service.clear(q.column.key)
            }
          })
          .then(function() {
            return service
          })
      }

    }
  }
}
<|endoftext|>


<commit_before>import {slice} from "./array";
import noop from "./noop";

var noabort = {},
    success = [null];

function newQueue(concurrency) {
  if (!(concurrency >= 1)) throw new Error;

  var q,
      tasks = [],
      results = [],
      waiting = 0,
      active = 0,
      ended = 0,
      starting, // inside a synchronous task callback?
      error,
      callback = noop,
      callbackAll = true;

  function start() {
    if (starting) return; // let the current task complete
    while (starting = waiting && active < concurrency) {
      var i = ended + active,
          t = tasks[i],
          j = t.length - 1,
          c = t[j];
      t[j] = end(i);
      --waiting, ++active;
      try { t = c.apply(null, t); }
      catch (e) { if (tasks[i]) return abort(e); } // threw before finishing?
      if (!tasks[i]) continue; // task finished synchronously
      tasks[i] = t || noabort;
    }
  }

  function end(i) {
    return function(e, r) {
      if (!tasks[i]) return; // ignore multiple callbacks
      --active, ++ended;
      tasks[i] = null;
      if (error != null) return; // only report the first error
      if (e != null) {
        abort(e);
      } else {
        results[i] = r;
        if (waiting) start();
        else if (!active) notify();
      }
    };
  }

  function abort(e) {
    var i = ended + active, t;
    error = e; // ignore new tasks and squelch active callbacks
    waiting = active = NaN; // prevent starting and allow notification
    while (--i >= 0) if ((t = tasks[i]) && t.abort) t.abort();
    notify();
  }

  function notify() {
    if (error != null) callback(error);
    else if (callbackAll) callback(null, results);
    else callback.apply(null, success.concat(results));
  }

  return q = {
    defer: function(f) {
      if (callback !== noop) throw new Error;
      var t = slice.call(arguments, 1);
      t.push(f);
      ++waiting, tasks.push(t);
      start();
      return q;
    },
    abort: function() {
      if (error == null) abort(new Error("abort"));
      return q;
    },
    await: function(f) {
      if (callback !== noop) throw new Error;
      callback = f, callbackAll = false;
      if (!waiting && !active) notify();
      return q;
    },
    awaitAll: function(f) {
      if (callback !== noop) throw new Error;
      callback = f, callbackAll = true;
      if (!waiting && !active) notify();
      return q;
    }
  };
}

export default function(concurrency) {
  return newQueue(arguments.length ? +concurrency : Infinity);
}
<commit_msg>Ignore errors in task.abort.<commit_after>import {slice} from "./array";
import noop from "./noop";

var noabort = {},
    success = [null];

function newQueue(concurrency) {
  if (!(concurrency >= 1)) throw new Error;

  var q,
      tasks = [],
      results = [],
      waiting = 0,
      active = 0,
      ended = 0,
      starting, // inside a synchronous task callback?
      error,
      callback = noop,
      callbackAll = true;

  function start() {
    if (starting) return; // let the current task complete
    while (starting = waiting && active < concurrency) {
      var i = ended + active,
          t = tasks[i],
          j = t.length - 1,
          c = t[j];
      t[j] = end(i);
      --waiting, ++active;
      try { t = c.apply(null, t); }
      catch (e) { if (tasks[i]) return abort(e); } // threw before finishing?
      if (!tasks[i]) continue; // task finished synchronously
      tasks[i] = t || noabort;
    }
  }

  function end(i) {
    return function(e, r) {
      if (error != null || !tasks[i]) return; // ignore multiple callbacks
      if (e != null) {
        abort(e);
      } else {
        --active, ++ended;
        tasks[i] = null;
        results[i] = r;
        if (waiting) start();
        else if (!active) notify();
      }
    };
  }

  function abort(e) {
    var i = ended + active, t;
    error = e; // ignore active callbacks
    waiting = active = NaN; // prevent starting and allow notification

    while (--i >= 0) {
      if ((t = tasks[i]) && t.abort) {
        try { t.abort(); }
        catch (e) { /* ignore */ }
      }
    }

    notify();
  }

  function notify() {
    if (error != null) callback(error);
    else if (callbackAll) callback(null, results);
    else callback.apply(null, success.concat(results));
  }

  return q = {
    defer: function(f) {
      if (callback !== noop) throw new Error;
      var t = slice.call(arguments, 1);
      t.push(f);
      ++waiting, tasks.push(t);
      start();
      return q;
    },
    abort: function() {
      if (error == null) abort(new Error("abort"));
      return q;
    },
    await: function(f) {
      if (callback !== noop) throw new Error;
      callback = f, callbackAll = false;
      if (!waiting && !active) notify();
      return q;
    },
    awaitAll: function(f) {
      if (callback !== noop) throw new Error;
      callback = f, callbackAll = true;
      if (!waiting && !active) notify();
      return q;
    }
  };
}

export default function(concurrency) {
  return newQueue(arguments.length ? +concurrency : Infinity);
}
<|endoftext|>


<commit_before>/**
 * @fileOverview 文件队列
 */
define([
    './base',
    './mediator',
    './file'
], function( Base, Mediator, WUFile ) {

    var $ = Base.$,
        STATUS = WUFile.Status;

    /**
     * 文件队列, 用来存储各个状态中的文件。
     * @class Queue
     * @extends Mediator
     */
    function Queue() {

        /**
         * 统计文件数。
         * * `numOfQueue` 队列中的文件数。
         * * `numOfSuccess` 上传成功的文件数
         * * `numOfCancel` 被取消的文件数
         * * `numOfProgress` 正在上传中的文件数
         * * `numOfUploadFailed` 上传错误的文件数。
         * * `numOfInvalid` 无效的文件数。
         * * `numOfDeleted` 被移除的文件数。
         * * `numOfInterrupt` 被中断的文件。
         * @property {Object} stats
         */
        this.stats = {
            numOfQueue: 0,
            numOfSuccess: 0,
            numOfCancel: 0,
            numOfProgress: 0,
            numOfUploadFailed: 0,
            numOfInvalid: 0,
            numOfDeleted: 0,
            numOfInterrupt: 0
        };

        // 上传队列，仅包括等待上传的文件
        this._queue = [];

        // 存储所有文件
        this._map = {};
    }

    $.extend( Queue.prototype, {

        /**
         * 将新文件加入对队列尾部
         *
         * @method append
         * @param  {File} file   文件对象
         */
        append: function( file ) {
            this._queue.push( file );
            this._fileAdded( file );
            return this;
        },

        /**
         * 将新文件加入对队列头部
         *
         * @method prepend
         * @param  {File} file   文件对象
         */
        prepend: function( file ) {
            this._queue.unshift( file );
            this._fileAdded( file );
            return this;
        },

        /**
         * 获取文件对象
         *
         * @method getFile
         * @param  {String} fileId   文件ID
         * @return {File}
         */
        getFile: function( fileId ) {
            if ( typeof fileId !== 'string' ) {
                return fileId;
            }
            return this._map[ fileId ];
        },

        /**
         * 从队列中取出一个指定状态的文件。
         * @grammar fetch( status ) => File
         * @method fetch
         * @param {String} status [文件状态值](#WebUploader:File:File.Status)
         * @return {File} [File](#WebUploader:File)
         */
        fetch: function( status ) {
            var len = this._queue.length,
                i, file;

            status = status || STATUS.QUEUED;

            for ( i = 0; i < len; i++ ) {
                file = this._queue[ i ];

                if ( status === file.getStatus() ) {
                    return file;
                }
            }

            return null;
        },

        /**
         * 对队列进行排序，能够控制文件上传顺序。
         * @grammar sort( fn ) => undefined
         * @method sort
         * @param {Function} fn 排序方法
         */
        sort: function( fn ) {
            if ( typeof fn === 'function' ) {
                this._queue.sort( fn );
            }
        },

        /**
         * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
         * @grammar getFiles( [status1[, status2 ...]] ) => Array
         * @method getFiles
         * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
         */
        getFiles: function() {
            var sts = [].slice.call( arguments, 0 ),
                ret = [],
                i = 0,
                len = this._queue.length,
                file;

            for ( ; i < len; i++ ) {
                file = this._queue[ i ];

                if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
                    continue;
                }

                ret.push( file );
            }

            return ret;
        },

        /**
         * 在队列中删除文件。
         * @grammar removeFile( file ) => Array
         * @method removeFile
         * @param {File} 文件对象。
         */
        removeFile: function( file ) {
            var me = this,
                existing = this._map[ file.id ];

            if ( existing ) {
                delete this._map[ file.id ];
                this._delFile(file);
                file.destroy();
                this.stats.numOfDeleted++;

            }
        },

        _fileAdded: function( file ) {
            var me = this,
                existing = this._map[ file.id ];

            if ( !existing ) {
                this._map[ file.id ] = file;

                file.on( 'statuschange', function( cur, pre ) {
                    me._onFileStatusChange( cur, pre );
                });
            }
        },

        _delFile : function(file){
            for(var i = this._queue.length - 1 ; i >= 0 ; i-- ){
                if(this._queue[i] == file){
                    this._queue.splice(i,1);
                    break;
                }
            }
        },

        _onFileStatusChange: function( curStatus, preStatus ) {
            var stats = this.stats;

            switch ( preStatus ) {
                case STATUS.PROGRESS:
                    stats.numOfProgress--;
                    break;

                case STATUS.QUEUED:
                    stats.numOfQueue --;
                    break;

                case STATUS.ERROR:
                    stats.numOfUploadFailed--;
                    break;

                case STATUS.INVALID:
                    stats.numOfInvalid--;
                    break;

                case STATUS.INTERRUPT:
                    stats.numOfInterrupt--;
                    break;
            }

            switch ( curStatus ) {
                case STATUS.QUEUED:
                    stats.numOfQueue++;
                    break;

                case STATUS.PROGRESS:
                    stats.numOfProgress++;
                    break;

                case STATUS.ERROR:
                    stats.numOfUploadFailed++;
                    break;

                case STATUS.COMPLETE:
                    stats.numOfSuccess++;
                    break;

                case STATUS.CANCELLED:
                    stats.numOfCancel++;
                    break;

                case STATUS.INVALID:
                    stats.numOfInvalid++;
                    break;

                case STATUS.INTERRUPT:
                    stats.numOfInterrupt++;
                    break;
            }
        }

    });

    Mediator.installTo( Queue.prototype );

    return Queue;
});
<commit_msg>Update queue.js<commit_after>/**
 * @fileOverview 文件队列
 */
define([
    './base',
    './mediator',
    './file'
], function( Base, Mediator, WUFile ) {

    var $ = Base.$,
        STATUS = WUFile.Status;

    /**
     * 文件队列, 用来存储各个状态中的文件。
     * @class Queue
     * @extends Mediator
     */
    function Queue() {

        /**
         * 统计文件数。
         * * `numOfQueue` 队列中的文件数。
         * * `numOfSuccess` 上传成功的文件数
         * * `numOfCancel` 被取消的文件数
         * * `numOfProgress` 正在上传中的文件数
         * * `numOfUploadFailed` 上传错误的文件数。
         * * `numOfInvalid` 无效的文件数。
         * * `numOfDeleted` 被移除的文件数。
         * * `numOfInterrupt` 被中断的文件数。
         * @property {Object} stats
         */
        this.stats = {
            numOfQueue: 0,
            numOfSuccess: 0,
            numOfCancel: 0,
            numOfProgress: 0,
            numOfUploadFailed: 0,
            numOfInvalid: 0,
            numOfDeleted: 0,
            numOfInterrupt: 0
        };

        // 上传队列，仅包括等待上传的文件
        this._queue = [];

        // 存储所有文件
        this._map = {};
    }

    $.extend( Queue.prototype, {

        /**
         * 将新文件加入对队列尾部
         *
         * @method append
         * @param  {File} file   文件对象
         */
        append: function( file ) {
            this._queue.push( file );
            this._fileAdded( file );
            return this;
        },

        /**
         * 将新文件加入对队列头部
         *
         * @method prepend
         * @param  {File} file   文件对象
         */
        prepend: function( file ) {
            this._queue.unshift( file );
            this._fileAdded( file );
            return this;
        },

        /**
         * 获取文件对象
         *
         * @method getFile
         * @param  {String} fileId   文件ID
         * @return {File}
         */
        getFile: function( fileId ) {
            if ( typeof fileId !== 'string' ) {
                return fileId;
            }
            return this._map[ fileId ];
        },

        /**
         * 从队列中取出一个指定状态的文件。
         * @grammar fetch( status ) => File
         * @method fetch
         * @param {String} status [文件状态值](#WebUploader:File:File.Status)
         * @return {File} [File](#WebUploader:File)
         */
        fetch: function( status ) {
            var len = this._queue.length,
                i, file;

            status = status || STATUS.QUEUED;

            for ( i = 0; i < len; i++ ) {
                file = this._queue[ i ];

                if ( status === file.getStatus() ) {
                    return file;
                }
            }

            return null;
        },

        /**
         * 对队列进行排序，能够控制文件上传顺序。
         * @grammar sort( fn ) => undefined
         * @method sort
         * @param {Function} fn 排序方法
         */
        sort: function( fn ) {
            if ( typeof fn === 'function' ) {
                this._queue.sort( fn );
            }
        },

        /**
         * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
         * @grammar getFiles( [status1[, status2 ...]] ) => Array
         * @method getFiles
         * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
         */
        getFiles: function() {
            var sts = [].slice.call( arguments, 0 ),
                ret = [],
                i = 0,
                len = this._queue.length,
                file;

            for ( ; i < len; i++ ) {
                file = this._queue[ i ];

                if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
                    continue;
                }

                ret.push( file );
            }

            return ret;
        },

        /**
         * 在队列中删除文件。
         * @grammar removeFile( file ) => Array
         * @method removeFile
         * @param {File} 文件对象。
         */
        removeFile: function( file ) {
            var me = this,
                existing = this._map[ file.id ];

            if ( existing ) {
                delete this._map[ file.id ];
                this._delFile(file);
                file.destroy();
                this.stats.numOfDeleted++;

            }
        },

        _fileAdded: function( file ) {
            var me = this,
                existing = this._map[ file.id ];

            if ( !existing ) {
                this._map[ file.id ] = file;

                file.on( 'statuschange', function( cur, pre ) {
                    me._onFileStatusChange( cur, pre );
                });
            }
        },

        _delFile : function(file){
            for(var i = this._queue.length - 1 ; i >= 0 ; i-- ){
                if(this._queue[i] == file){
                    this._queue.splice(i,1);
                    break;
                }
            }
        },

        _onFileStatusChange: function( curStatus, preStatus ) {
            var stats = this.stats;

            switch ( preStatus ) {
                case STATUS.PROGRESS:
                    stats.numOfProgress--;
                    break;

                case STATUS.QUEUED:
                    stats.numOfQueue --;
                    break;

                case STATUS.ERROR:
                    stats.numOfUploadFailed--;
                    break;

                case STATUS.INVALID:
                    stats.numOfInvalid--;
                    break;

                case STATUS.INTERRUPT:
                    stats.numOfInterrupt--;
                    break;
            }

            switch ( curStatus ) {
                case STATUS.QUEUED:
                    stats.numOfQueue++;
                    break;

                case STATUS.PROGRESS:
                    stats.numOfProgress++;
                    break;

                case STATUS.ERROR:
                    stats.numOfUploadFailed++;
                    break;

                case STATUS.COMPLETE:
                    stats.numOfSuccess++;
                    break;

                case STATUS.CANCELLED:
                    stats.numOfCancel++;
                    break;

                case STATUS.INVALID:
                    stats.numOfInvalid++;
                    break;

                case STATUS.INTERRUPT:
                    stats.numOfInterrupt++;
                    break;
            }
        }

    });

    Mediator.installTo( Queue.prototype );

    return Queue;
});
<|endoftext|>


<commit_before>!function (context, doc) {

  function array(ar) {
    return Array.prototype.slice.call(ar, 0);
  }

  function getAllChildren(e) {
    return e.all ? e.all : e.getElementsByTagName('*');
  }

  function iter(obj) {
    this.obj = array(obj);
  }

  iter.prototype = {
    each: function (fn) {
      for (var i = 0; i  < this.obj.length; i ++) {
        fn.call(this.obj[i], this.obj[i], i, this.obj);
      }
      return this;
    },

    map: function (fn) {
      var collection = [];
      for (var i = 0; i  < this.obj.length; i ++) {
        collection[i] = fn.call(this.obj[i], this.obj[i], i, this.obj);
      }
      return collection;
    }
  };

  function _(obj) {
    return new iter(obj);
  }

  var checkFunctions = {
    '=': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName) == attrValue);
    },
    '~': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName).match(new RegExp('\\b' + attrValue + '\\b')));
    },
    '|': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName).match(new RegExp('^' + attrValue + '-?')));
    },
    '^': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName).indexOf(attrValue) === 0);
    },
    '$': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length);
    },
    '*': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName).indexOf(attrValue) > -1);
    },
    '': function (e, attrName) {
      return e.getAttribute(attrName);
    }
  };

  function isAncestor(child, parent) {
    if (!parent || !child) {
      return false;
    }
    if (parent.contains && child.nodeType) {
      return parent.contains(child);
    }
    else if (parent.compareDocumentPosition && child.nodeType) {
      return !!(parent.compareDocumentPosition(child) & 16);
    }
    return false;
  }

  function _qwery(selector) {
    var tokens = selector.split(' '), bits, tagName, h, i, j, k, l, len,
      found, foundCount, elements, currentContextIndex, currentContext = [doc];

    for (i = 0, l = tokens.length; i < l; i++) {
      token = tokens[i].replace(/^\s+|\s+$/g, '');

      if (token.indexOf('#') > -1) {
        bits = token.split('#');
        tagName = bits[0];
        var element = doc.getElementById(bits[1]);
        if (tagName && element.nodeName.toLowerCase() != tagName) {
          return [];
        }
        currentContext = [element];
        continue;
      }

      if (token.indexOf('.') > -1) {
        // Token contains a class selector
        bits = token.split('.');
        tagName = bits[foundCount = 0];
        tagName = tagName || '*';
        found = [];
        for (h = 0, len = currentContext.length; h < len; h++) {
          elements = tagName == '*' ? getAllChildren(currentContext[h]) : currentContext[h].getElementsByTagName(tagName);
          for (j = 0, k = elements.length; j < k; j++) {
            found[foundCount++] = elements[j];
          }
        }
        currentContext = [];
        currentContextIndex = 0;
        for (k = 0, len = found.length; k < len; k++) {
          if (found[k].className && found[k].className.match(new RegExp('(?:^|\\s+)' + bits[1] + '(?:\\s+|$)'))) {
            currentContext[currentContextIndex++] = found[k];
          }
        }
        continue;
      }
      // Code to deal with attribute selectors
      var match = token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/);
      if (match) {
        tagName = match[1];
        var attrName = match[2];
        var attrOperator = match[3];
        var attrValue = match[4];
        if (!tagName) {
          tagName = '*';
        }
        // Grab all of the tagName elements within current context
        found = [];
        foundCount = 0;
        for (h = 0; h < currentContext.length; h++) {
          if (tagName == '*') {
            elements = getAllChildren(currentContext[h]);
          } else {
            elements = currentContext[h].getElementsByTagName(tagName);
          }
          for (j = 0; j < elements.length; j++) {
            found[foundCount++] = elements[j];
          }
        }
        currentContext = [];
        currentContextIndex = 0;
        // This function will be used to filter the elements
        var checkFunction = checkFunctions[attrOperator] || checkFunctions[''];
        for (k = 0; k < found.length; k++) {
          if (checkFunction(found[k], attrName, attrValue)) {
            currentContext[currentContextIndex++] = found[k];
          }
        }
        continue; // Skip to next token
      }
      // If we get here, token is JUST an element (not a class or ID selector)
      tagName = token;
      found = [];
      foundCount = 0;

      for (h = 0; h < currentContext.length; h++) {
        elements = currentContext[h].getElementsByTagName(tagName);
        for (j = 0; j < elements.length; j++) {
          found[foundCount++] = elements[j];
        }
      }
      currentContext = found;
    }
    return currentContext;
  }

  var qwery = function () {

    // exception for pure classname selectors (it's faster)
    var clas = /^\.([\w\-]+)$/, m;

    function qsa(selector, root) {
      // taking for granted that every browser that supports qsa, also supports getElsByClsName
      if (m = selector.match(clas)) {
        return array((root || document).getElementsByClassName(m[1]), 0);
      }
      return array((root || document).querySelectorAll(selector), 0);
    }

    // return fast
    if (document.querySelectorAll) {
      return qsa;
    }

    return function (selector, root) {
      root = root == 'sring' ? qwery(root)[0] : root || document;
      // these next two operations could really benefit from an accumulator (eg: map/each/accumulate)
      var result = [];
      // here we allow combinator selectors: $('div,span');
      var collections = _(selector.split(',')).map(function (selector) {
        return _qwery(selector);
      });

      _(collections).each(function (collection) {
        var ret = collection;
        // allow contexts
        if (root !== document) {
          ret = [];
          _(collection).each(function (element) {
            // make sure element is a descendent of root
            isAncestor(element, root) && ret.push(element);
          });
        }

        result = result.concat(ret);
      });
      return result;
    };
  }();

  var oldQwery = context.qwery;

  // being nice
  qwery.noConflict = function () {
    context.qwery = oldQwery;
    return this;
  };
  context.qwery = qwery;

}(this, document);<commit_msg>fix getAttribute bug (thanks jdalton)<commit_after>!function (context, doc) {

  function array(ar) {
    return Array.prototype.slice.call(ar, 0);
  }

  function getAllChildren(e) {
    return e.all ? e.all : e.getElementsByTagName('*');
  }

  function iter(obj) {
    this.obj = array(obj);
  }

  iter.prototype = {
    each: function (fn) {
      for (var i = 0; i  < this.obj.length; i ++) {
        fn.call(this.obj[i], this.obj[i], i, this.obj);
      }
      return this;
    },

    map: function (fn) {
      var collection = [];
      for (var i = 0; i  < this.obj.length; i ++) {
        collection[i] = fn.call(this.obj[i], this.obj[i], i, this.obj);
      }
      return collection;
    }
  };

  function _(obj) {
    return new iter(obj);
  }

  function getAttribute(e, attrName) {
    return e.getAttribute(attrName) || '';
  }

  var checkFunctions = {
    '=': function (e, attrName, attrValue) {
      return (e.getAttribute(attrName) == attrValue);
    },
    '~': function (e, attrName, attrValue) {
      return (getAttribute(e, attrName).match(new RegExp('\\b' + attrValue + '\\b')));
    },
    '|': function (e, attrName, attrValue) {
      return (getAttribute(e, attrName).match(new RegExp('^' + attrValue + '-?')));
    },
    '^': function (e, attrName, attrValue) {
      return (getAttribute(e, attrName).indexOf(attrValue) === 0);
    },
    '$': function (e, attrName, attrValue) {
      return (getAttribute(e, attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length);
    },
    '*': function (e, attrName, attrValue) {
      return (getAttribute(e, attrName).indexOf(attrValue) > -1);
    },
    '': function (e, attrName) {
      return e.getAttribute(attrName);
    }
  };

  function isAncestor(child, parent) {
    if (!parent || !child) {
      return false;
    }
    if (parent.contains && child.nodeType) {
      return parent.contains(child);
    }
    else if (parent.compareDocumentPosition && child.nodeType) {
      return !!(parent.compareDocumentPosition(child) & 16);
    }
    return false;
  }

  function _qwery(selector) {
    var tokens = selector.split(' '), bits, tagName, h, i, j, k, l, len,
      found, foundCount, elements, currentContextIndex, currentContext = [doc];

    for (i = 0, l = tokens.length; i < l; i++) {
      token = tokens[i].replace(/^\s+|\s+$/g, '');

      if (token.indexOf('#') > -1) {
        bits = token.split('#');
        tagName = bits[0];
        var element = doc.getElementById(bits[1]);
        if (tagName && element.nodeName.toLowerCase() != tagName) {
          return [];
        }
        currentContext = [element];
        continue;
      }

      if (token.indexOf('.') > -1) {
        // Token contains a class selector
        bits = token.split('.');
        tagName = bits[foundCount = 0];
        tagName = tagName || '*';
        found = [];
        for (h = 0, len = currentContext.length; h < len; h++) {
          elements = tagName == '*' ? getAllChildren(currentContext[h]) : currentContext[h].getElementsByTagName(tagName);
          for (j = 0, k = elements.length; j < k; j++) {
            found[foundCount++] = elements[j];
          }
        }
        currentContext = [];
        currentContextIndex = 0;
        for (k = 0, len = found.length; k < len; k++) {
          if (found[k].className && found[k].className.match(new RegExp('(?:^|\\s+)' + bits[1] + '(?:\\s+|$)'))) {
            currentContext[currentContextIndex++] = found[k];
          }
        }
        continue;
      }
      // Code to deal with attribute selectors
      var match = token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/);
      if (match) {
        tagName = match[1];
        var attrName = match[2];
        var attrOperator = match[3];
        var attrValue = match[4];
        if (!tagName) {
          tagName = '*';
        }
        // Grab all of the tagName elements within current context
        found = [];
        foundCount = 0;
        for (h = 0; h < currentContext.length; h++) {
          if (tagName == '*') {
            elements = getAllChildren(currentContext[h]);
          } else {
            elements = currentContext[h].getElementsByTagName(tagName);
          }
          for (j = 0; j < elements.length; j++) {
            found[foundCount++] = elements[j];
          }
        }
        currentContext = [];
        currentContextIndex = 0;
        // This function will be used to filter the elements
        var checkFunction = checkFunctions[attrOperator] || checkFunctions[''];
        for (k = 0; k < found.length; k++) {
          if (checkFunction(found[k], attrName, attrValue)) {
            currentContext[currentContextIndex++] = found[k];
          }
        }
        continue; // Skip to next token
      }
      // If we get here, token is JUST an element (not a class or ID selector)
      tagName = token;
      found = [];
      foundCount = 0;

      for (h = 0; h < currentContext.length; h++) {
        elements = currentContext[h].getElementsByTagName(tagName);
        for (j = 0; j < elements.length; j++) {
          found[foundCount++] = elements[j];
        }
      }
      currentContext = found;
    }
    return currentContext;
  }

  var qwery = function () {

    // exception for pure classname selectors (it's faster)
    var clas = /^\.([\w\-]+)$/, m;

    function qsa(selector, root) {
      // taking for granted that every browser that supports qsa, also supports getElsByClsName
      if (m = selector.match(clas)) {
        return array((root || document).getElementsByClassName(m[1]), 0);
      }
      return array((root || document).querySelectorAll(selector), 0);
    }

    // return fast
    if (document.querySelectorAll) {
      return qsa;
    }

    return function (selector, root) {
      root = root == 'sring' ? qwery(root)[0] : root || document;
      // these next two operations could really benefit from an accumulator (eg: map/each/accumulate)
      var result = [];
      // here we allow combinator selectors: $('div,span');
      var collections = _(selector.split(',')).map(function (selector) {
        return _qwery(selector);
      });

      _(collections).each(function (collection) {
        var ret = collection;
        // allow contexts
        if (root !== document) {
          ret = [];
          _(collection).each(function (element) {
            // make sure element is a descendent of root
            isAncestor(element, root) && ret.push(element);
          });
        }

        result = result.concat(ret);
      });
      return result;
    };
  }();

  var oldQwery = context.qwery;

  // being nice
  qwery.noConflict = function () {
    context.qwery = oldQwery;
    return this;
  };
  context.qwery = qwery;

}(this, document);<|endoftext|>


<commit_before>/*
 * jquery-ujs
 *
 * http://github.com/rails/jquery-ujs/blob/master/src/rails.js
 *
 * This rails.js file supports jQuery 1.4.3 and 1.4.4 .
 *
 */

jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls. Provides following callbacks:
         *
         * - ajax:before   - is execute before the whole thing begings
         * - ajax:loading  - is executed before firing ajax call
         * - ajax:success  - is executed when status is success
         * - ajax:complete - is execute when status is complete
         * - ajax:failure  - is execute in case of error
         * - ajax:after    - is execute every single time at the end of ajax call
         */
        callRemote: function () {
            var el      = this,
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href'),
                dataType  = el.attr('data-type')  || 'script';

            if (url === undefined) {
              throw "No URL specified for remote call (action or href must be present).";
            } else {
                if (el.triggerAndReturn('ajax:before')) {
                    var data = el.is('form') ? el.serializeArray() : [];
                    $.ajax({
                        url: url,
                        data: data,
                        dataType: dataType,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */

    $('body').delegate('a[data-confirm], button[data-confirm], input[data-confirm]', 'click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote],input[data-remote]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-method]:not([data-remote])').live('click', function (e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'"></form>'),
            metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

        if (csrf_param !== undefined && csrf_token !== undefined) {
          metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
        }

        form.hide()
            .append(metadata_input)
            .appendTo('body');

        e.preventDefault();
        form.submit();
    });

    /**
     * disable-with handlers
     */
    var disable_with_input_selector           = 'input[data-disable-with]',
        disable_with_form_remote_selector     = 'form[data-remote]:has('       + disable_with_input_selector + ')',
        disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';

    var disable_with_input_function = function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.data('enable-with', input.val())
                .attr('value', input.attr('data-disable-with'))
                .attr('disabled', 'disabled');
        });
    };

    $(disable_with_form_remote_selector).live('ajax:before', disable_with_input_function);
    $(disable_with_form_not_remote_selector).live('submit', disable_with_input_function);

    $(disable_with_form_remote_selector).live('ajax:complete', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.removeAttr('disabled')
                 .val(input.data('enable-with'));
        });
    });

    var jqueryVersion = $().jquery;

    if ( (jqueryVersion === '1.4') || (jqueryVersion === '1.4.1') || (jqueryVersion === '1.4.2') ){
      alert('This rails.js does not support the jQuery version you are using. Please read documentation.')
    }

});
<commit_msg>add ; to fix lint warning<commit_after>/*
 * jquery-ujs
 *
 * http://github.com/rails/jquery-ujs/blob/master/src/rails.js
 *
 * This rails.js file supports jQuery 1.4.3 and 1.4.4 .
 *
 */

jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls. Provides following callbacks:
         *
         * - ajax:before   - is execute before the whole thing begings
         * - ajax:loading  - is executed before firing ajax call
         * - ajax:success  - is executed when status is success
         * - ajax:complete - is execute when status is complete
         * - ajax:failure  - is execute in case of error
         * - ajax:after    - is execute every single time at the end of ajax call
         */
        callRemote: function () {
            var el      = this,
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href'),
                dataType  = el.attr('data-type')  || 'script';

            if (url === undefined) {
              throw "No URL specified for remote call (action or href must be present).";
            } else {
                if (el.triggerAndReturn('ajax:before')) {
                    var data = el.is('form') ? el.serializeArray() : [];
                    $.ajax({
                        url: url,
                        data: data,
                        dataType: dataType,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */

    $('body').delegate('a[data-confirm], button[data-confirm], input[data-confirm]', 'click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote],input[data-remote]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-method]:not([data-remote])').live('click', function (e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'"></form>'),
            metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

        if (csrf_param !== undefined && csrf_token !== undefined) {
          metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
        }

        form.hide()
            .append(metadata_input)
            .appendTo('body');

        e.preventDefault();
        form.submit();
    });

    /**
     * disable-with handlers
     */
    var disable_with_input_selector           = 'input[data-disable-with]',
        disable_with_form_remote_selector     = 'form[data-remote]:has('       + disable_with_input_selector + ')',
        disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';

    var disable_with_input_function = function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.data('enable-with', input.val())
                .attr('value', input.attr('data-disable-with'))
                .attr('disabled', 'disabled');
        });
    };

    $(disable_with_form_remote_selector).live('ajax:before', disable_with_input_function);
    $(disable_with_form_not_remote_selector).live('submit', disable_with_input_function);

    $(disable_with_form_remote_selector).live('ajax:complete', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.removeAttr('disabled')
                 .val(input.data('enable-with'));
        });
    });

    var jqueryVersion = $().jquery;

    if ( (jqueryVersion === '1.4') || (jqueryVersion === '1.4.1') || (jqueryVersion === '1.4.2') ){
      alert('This rails.js does not support the jQuery version you are using. Please read documentation.');
    }

});
<|endoftext|>