Source: bluefile.js

/**
 * @license
 * File: bluefile.js
 * Copyright (c) 2012-2014, Michael Ihde, All rights reserved.
 * Copyright (c) 2012-2014, Axios Inc., All rights reserved.
 *
 * This file is part of SigPlot.
 *
 * SigPlot is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation; either version 3.0 of the License, or
 * (at your option) any later version. This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the
 * GNU Lesser General Public License along with SigPlot.
 *
 */

/**
 * Bluefiles are a binary format directly supported by SigPlot.  A Bluefile consists of a 512-byte header
 * followed by binary data.
 * For more information on BLUEFILES, please visit http://nextmidas.techma.com/nm/htdocs/usersguide/BlueFiles.html
 *
 * Offset	Name		Size	Type		Description
 * -----------------------------------------------------------------------------
 * 0 		version 	4 	char[4] 	Header version
 * 4 		head_rep 	4 	char[4] 	Header representation
 * 8 		data_rep 	4 	char[4] 	Data representation
 *12 		detached 	4 	int_4 		Detached header
 *16 		protected 	4 	int_4 		Protected from overwrite
 *20 		pipe 		4 	int_4 		Pipe mode (N/A)
 *24 		ext_start 	4 	int_4 		Extended header start, in 512-byte blocks
 *28 		ext_size 	4 	int_4 		Extended header size in bytes
 *32 		data_start 	8 	real_8 		Data start in bytes
 *40 		data_size 	8 	real_8 		Data size in bytes
 *48 		type 		4 	int_4 		File type code
 *52 		format 		2 	char[2] 	Data format code
 *54 		flagmask 	2 	int_2 		16-bit flagmask (1=flagbit)
 *56 		timecode 	8 	real_8 		Time code field
 *64 		inlet 		2 	int_2 		Inlet owner
 *66 		outlets 	2 	int_2 		Number of outlets
 *68 		outmask 	4 	int_4 		Outlet async mask
 *72 		pipeloc 	4 	int_4 		Pipe location
 *76 		pipesize 	4 	int_4 		Pipe size in bytes
 *80 		in_byte 	8 	real_8 		Next input byte
 *88 		out_byte 	8 	real_8 		Next out byte (cumulative)
 *96 		outbytes 	64 	real_8[8] 	Next out byte (each outlet)
 *160 		keylength 	4 	int_4 		Length of keyword string
 *164 		keywords 	92 	char[92] 	User defined keyword string
 *256 		Adjunct 	256 	char[256] 	Type-specific adjunct union (See below for 1000 and 2000 type bluefiles)
 *
 *
 * Type-1000 Adjunct
 * 0 		xstart 		8 	real_8 		Abscissa value for first sample
 *8 		xdelta 		8 	real_8 		Abscissa interval between samples
 *16 		xunits 		4 	int_4 		Units for abscissa values
 *
 * Type-2000 Adjunct
 *0 		xstart 		8 	real_8 		Frame (column) starting value
 *8 		xdelta 		8 	real_8 		Increment between samples in frame
 *16 		xunits 		4 	int_4 		Frame (column) units
 *20 		subsize 	4 	int_4 		Number of data points per frame (row)
 *24 		ystart 		8 	real_8 		Abscissa (row) start
 *32 		ydelta 		8 	real_8 		Increment between frames
 *36 		yunits 		4 	int_4 		Abscissa (row) unit code
 *
 * @namespace bluefile
 */
(function(global) {
    'use strict';

    /**
     * @memberOf bluefile
     * @private
     */
    var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/i) ? true : false);



    // https://gist.github.com/TooTallNate/4750953
    /**
     * @memberof bluefile
     * @private
     */
    function endianness() {
        var b = new ArrayBuffer(4);
        var a = new Uint32Array(b);
        var c = new Uint8Array(b);
        a[0] = 0xdeadbeef;
        if (c[0] === 0xef) { return 'LE'; }
        if (c[0] === 0xde) { return 'BE'; }
        throw new Error('unknown endianness');
    }




    /**
     * @memberOf bluefile
     * @private
     */
    var ARRAY_BUFFER_ENDIANNESS = endianness();



    /**
     * @memberOf bluefile
     * @private
     */
    var _SPA = {
        'S': 1,
        'C': 2,
        'V': 3,
        'Q': 4,
        'M': 9,
        'X': 10,
        'T': 16,
        'U': 1,
        '1': 1,
        '2': 2,
        '3': 3,
        '4': 4,
        '5': 5,
        '6': 6,
        '7': 7,
        '8': 8,
        '9': 9
    };



    /**
     * @memberOf bluefile
     * @private
     */
    var _BPS = {
        'P': 0.125,
        'A': 1,
        'O': 1,
        'B': 1,
        'I': 2,
        'L': 4,
        'X': 8,
        'F': 4,
        'D': 8
    };



    /**
     * @memberOf bluefile
     * @private
     */
    var _XM_TO_TYPEDARRAY = {
        'P': null,
        'A': null,
        'O': Uint8Array,
        'B': Int8Array,
        'I': Int16Array,
        'L': Int32Array,
        'X': null,
        'F': Float32Array,
        'D': Float64Array
    };



    /**
     * @memberOf bluefile
     * @private
     */
    var _applySupportsTypedArray = true;
    try {
        var uintbuf = new UInt8Array(new ArrayBuffer(4));
        uintbuf[0] = 66;
        uintbuf[1] = 76;
        uintbuf[2] = 85;
        uintbuf[3] = 69;
        var test = String.fromCharCode.apply(null, uintbuf);
        if (test !== "BLUE") {
            _applySupportsTypedArray = false;
        }
    } catch (error) {
        _applySupportsTypedArray = false;
    }



    /**
     * @memberof bluefile
     * @param 	{array} 	buf 		Data bffer
     * @private
     */
    function ab2str(buf) {
        var uintbuf = new Uint8Array(buf);
        // Firefox 3.6 nor iOS devices can use ArrayBuffers with .apply
        if (_applySupportsTypedArray) {
            return String.fromCharCode.apply(null, uintbuf);
        } else {
            var str = "";
            for (var i = 0; i < uintbuf.length; i++) {
                str += String.fromCharCode(uintbuf[i]);
            }
            return str;
        }
    }

    /**
     * Create bluefile header and attach data buffer
     * @memberof bluefile
     * @param 	{array} 	buf 		Data bffer
     */
    function BlueHeader(buf) {
        this.file = null;
        this.file_name = null;
        this.offset = 0;
        this.buf = buf;
        if (this.buf != null) {
            var dvhdr = new DataView(this.buf);
            this.version = ab2str(this.buf.slice(0, 4));
            this.headrep = ab2str(this.buf.slice(4, 8));
            this.datarep = ab2str(this.buf.slice(8, 12));
            var littleEndianHdr = (this.headrep === "EEEI");
            var littleEndianData = (this.datarep === "EEEI");
            this.type = dvhdr.getUint32(48, littleEndianHdr);
            this["class"] = this.type / 1000;
            this.format = ab2str(this.buf.slice(52, 54));
            this.timecode = dvhdr.getFloat64(56, littleEndianHdr);

            // the adjunct starts at offset 0x100
            if (this["class"] === 1) {
                this.xstart = dvhdr.getFloat64(0x100, littleEndianHdr);
                this.xdelta = dvhdr.getFloat64(0x100 + 8, littleEndianHdr);
                this.xunits = dvhdr.getInt32(0x100 + 16, littleEndianHdr);
                this.subsize = 1;
            } else if (this["class"] === 2) {
                this.xstart = dvhdr.getFloat64(0x100, littleEndianHdr);
                this.xdelta = dvhdr.getFloat64(0x100 + 8, littleEndianHdr);
                this.xunits = dvhdr.getInt32(0x100 + 16, littleEndianHdr);
                this.subsize = dvhdr.getInt32(0x100 + 20, littleEndianHdr);
                this.ystart = dvhdr.getFloat64(0x100 + 24, littleEndianHdr);
                this.ydelta = dvhdr.getFloat64(0x100 + 32, littleEndianHdr);
                this.yunits = dvhdr.getInt32(0x100 + 40, littleEndianHdr);
            }

            this.data_start = dvhdr.getFloat64(32, littleEndianHdr);
            this.data_size = dvhdr.getFloat64(40, littleEndianHdr);
            var ds = this.data_start;
            var de = this.data_start + this.data_size;
            this.setData(this.buf, ds, de, littleEndianData);
        }
    }

    BlueHeader.prototype = {
        /**
         * @memberof bluefile
         * @param 	buf
         * @param 	offset
         * @param 	data_end
         * @param 	littleEndian
         *
         */
        setData: function(buf, offset, data_end, littleEndian) {
            if (this["class"] === 1) {
                this.spa = _SPA[this.format[0]];
                this.bps = _BPS[this.format[1]];
                this.bpa = this.spa * this.bps;
                this.ape = 1;
                this.bpe = this.ape * this.bpa;
            } else if (this["class"] === 2) {
                this.spa = _SPA[this.format[0]];
                this.bps = _BPS[this.format[1]];
                this.bpa = this.spa * this.bps;
                this.ape = this.subsize;
                this.bpe = this.ape * this.bpa;
            }

            if (littleEndian === undefined) {
                littleEndian = (ARRAY_BUFFER_ENDIANNESS === "LE");
            }

            // TODO handle mismatch between host and data endianness using arrayBufferEndianness
            if (ARRAY_BUFFER_ENDIANNESS === "LE" && !littleEndian) {
                throw ("Not supported " + ARRAY_BUFFER_ENDIANNESS + " " + littleEndian);
            } else if (ARRAY_BUFFER_ENDIANNESS === "BE" && this.littleEndianData) {
                throw ("Not supported " + ARRAY_BUFFER_ENDIANNESS + " " + littleEndian);
            }

            if (buf) {
                if ((offset) && (data_end)) {
                    this.dview = this.createArray(buf, offset, (data_end - offset) / this.bps);
                } else {
                    this.dview = this.createArray(buf);
                }
                this.size = this.dview.length / (this.spa * this.ape);
            } else {
                this.dview = this.createArray(null, null, this.size);
            }
        },

        /**
         * Create typed array
         * @memberof bluefile
         * @param 	buf
         * @param 	offset
         * @param 	length
         * @returns -
         */
        createArray: function(buf, offset, length) {
            var TypedArray = _XM_TO_TYPEDARRAY[this.format[1]];
            if (TypedArray === undefined) {
                throw ("unknown format " + this.format[1]);
            }
            // backwards compatibility with some implementations of typed array
            // requires this
            if (offset === undefined) {
                offset = 0;
            }
            if (length === undefined) {
                length = buf.length || (buf.byteLength / _BPS[this.format[1]]);
            }

            if (buf) {
                return new TypedArray(buf, offset, length);
            } else {
                return new TypedArray(length);
            }
        }
    };


    // Internal method from http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
    /**
     * This function creates a new anchor element and uses location
     * properties (inherent) to get the desired URL data. Some String
     * operations are used (to normalize results across browsers).
     *
     * @memberof bluefile
     * @private
     * @param 	url
     * @returns -
     */
    function parseURL(url) {
        var a = document.createElement('a');
        a.href = url;
        return {
            source: url,
            protocol: a.protocol.replace(':', ''),
            host: a.hostname,
            port: a.port,
            query: a.search,
            params: (function() {
                var ret = {},
                    seg = a.search.replace(/^\?/, '').split('&'),
                    len = seg.length,
                    i = 0,
                    s;
                for (; i < len; i++) {
                    if (!seg[i]) {
                        continue;
                    }
                    s = seg[i].split('=');
                    ret[s[0]] = s[1];
                }
                return ret;
            })(),
            file: (a.pathname.match(/\/([^\/?#]+)$/i) || [null, ''])[1],
            hash: a.hash.replace('#', ''),
            path: a.pathname.replace(/^([^\/])/, '/$1'),
            relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [null, ''])[1],
            segments: a.pathname.replace(/^\//, '').split('/')
        };
    }

    /**
     * @memberof bluefile
     * @private
     * @param 	text
     * @param	oncomplete
     * @param 	blocksize
     */
    function text2buffer(text, oncomplete, blocksize) {
        blocksize = blocksize || 1024;
        var i = 0;
        var arrayBuffer = new ArrayBuffer(text.length);
        var bufView = new Uint8Array(arrayBuffer);
        var worker = function() {
            var end = i + blocksize;
            for (; i < end; i++) {
                bufView[i] = (text.charCodeAt(i) & 0xff);
            }
            if (i >= text.length) {
                oncomplete(arrayBuffer);
            } else {
                setTimeout(worker, 0);
            }
        };
        setTimeout(worker, 0);
    }


    /**
     * Bluefile Reader
     * @memberof	bluefile
     * @param 	options
     */
    function BlueFileReader(options) {
        this.options = options;
    }

    BlueFileReader.prototype = {

        /**
         * @memberof bluefile
         * @param 	theFile
         * @param 	onload
         *
         */
        readheader: function readheader(theFile, onload) {
            var reader = new FileReader();
            var blob = theFile.webkitSlice(0, 512); // Chrome specific

            // Closure to capture the file information.
            reader.onloadend = (function(theFile) {
                return function(e) {
                    if (e.target.error) {
                        onload(null);
                        return;
                    }

                    var rawhdr = reader.result;
                    var hdr = new BlueHeader(rawhdr);
                    hdr.file = theFile;
                    onload(hdr);
                };
            })(theFile);

            reader.readAsArrayBuffer(blob);
        },

        /**
         *
         * @memberof bluefile
         * @param	theFile
         * @param	onload
         *
         */
        read: function read(theFile, onload) {
            var reader = new FileReader();

            // Closure to capture the file information.
            reader.onloadend = (function(theFile) {
                return function(e) {
                    if (e.target.error) {
                        onload(null);
                        return;
                    }

                    var raw = reader.result;
                    var hdr = new BlueHeader(raw);
                    hdr.file = theFile;
                    hdr.file_name = theFile.name;
                    onload(hdr);
                };
            })(theFile);

            reader.readAsArrayBuffer(theFile);
        },


        /**
         *
         * @memberof bluefile
         * @param	href
         * @param	onload
         *
         */
        read_http: function read_http(href, onload) {
            var oReq = new XMLHttpRequest();
            oReq.open("GET", href, true);
            oReq.responseType = "arraybuffer";
            oReq.overrideMimeType('text\/plain; charset=x-user-defined');
            oReq.onload = function(oEvent) {
                if (oReq.readyState === 4) {
                    if ((oReq.status === 200) || (oReq.status === 0)) { // status = 0 is necessary for file URL
                        var arrayBuffer = null; // Note: not oReq.responseText
                        if (oReq.response) {
                            arrayBuffer = oReq.response;
                            var hdr = new BlueHeader(arrayBuffer);
                            parseURL(href);
                            var fileUrl = parseURL(href);
                            hdr.file_name = fileUrl.file;
                            onload(hdr);
                        } else if (oReq.responseText) {
                            text2buffer(oReq.responseText, function(arrayBuffer) {
                                var hdr = new BlueHeader(arrayBuffer);
                                parseURL(href);
                                var fileUrl = parseURL(href);
                                hdr.file_name = fileUrl.file;
                                onload(hdr);
                            });
                        }
                        return;
                    }
                }
                onload(null);
            };

            oReq.onerror = function(oEvent) {
                onload(null);
            };

            oReq.send(null);
        }

    };

    global['BlueHeader'] = global['BlueHeader'] || BlueHeader;
    global['BlueFileReader'] = global['BlueFileReader'] || BlueFileReader;
}(this));
DocStrap Copyright © 2012-2013 The contributors to the JSDoc3 and DocStrap projects.
Documentation generated by JSDoc 3.2.2 on Sun Nov 02 2014 20:42:48 GMT-0000 (GMT) using the DocStrap template.