(function (self) {
    var min = Math.min,
        max = Math.max;

    var setString = function (view, offset, str) {
        var len = str.length;
        for (var i = 0; i < len; ++i)
            view.setUint8(offset + i, str.charCodeAt(i));
    };

    var Encoder = function (sampleRate, numChannels) {
        this.sampleRate = sampleRate;
        this.numChannels = numChannels;
        this.numSamples = 0;
        this.dataViews = [];
    };

    Encoder.prototype.encode = function (buffer) {
        var len = buffer.length,
            nCh = this.numChannels,
            view = new DataView(new ArrayBuffer(len * nCh * 2)),
            offset = 0;
        for (var i = 0; i < len; ++i)
            for (var ch = 0; ch < nCh; ++ch) {
                var x = buffer[ch][i] * 0x7fff;
                view.setInt16(offset, x < 0 ? max(x, -0x8000) : min(x, 0x7fff), true);
                offset += 2;
            }
        this.dataViews.push(view);
        this.numSamples += len;
    };

    Encoder.prototype.finish = function (mimeType) {
        var dataSize = this.numChannels * this.numSamples * 2,
            view = new DataView(new ArrayBuffer(44));
        setString(view, 0, 'RIFF');
        view.setUint32(4, 36 + dataSize, true);
        setString(view, 8, 'WAVE');
        setString(view, 12, 'fmt ');
        view.setUint32(16, 16, true);
        view.setUint16(20, 1, true);
        view.setUint16(22, this.numChannels, true);
        view.setUint32(24, this.sampleRate, true);
        view.setUint32(28, this.sampleRate * 4, true);
        view.setUint16(32, this.numChannels * 2, true);
        view.setUint16(34, 16, true);
        setString(view, 36, 'data');
        view.setUint32(40, dataSize, true);
        this.dataViews.unshift(view);
        var blob = new Blob(this.dataViews, { type: 'audio/wav' });
        this.cleanup();
        return blob;
    };

    Encoder.prototype.cancel = Encoder.prototype.cleanup = function () {
        delete this.dataViews;
    };

    self.WavAudioEncoder = Encoder;
})(self);