<commit_before>import {IGVColor} from "../../../node_modules/igv-utils/src/index.js";

import {gffNameFields, parseAttributeString} from "./gff.js";

/**
 * Decode the UCSC bed format.  Only the first 3 columns (chr, start, end) are required.   The remaining columns
 * must follow standard bed order, but we will tolerate deviations after column 3.
 *
 * @param tokens
 * @param ignore
 * @returns decoded feature, or null if this is not a valid record
 */
function decodeBed(tokens, header) {

    if (tokens.length < 3) return undefined;

    const gffTags = header && header.gffTags;

    const chr = tokens[0];
    const start = parseInt(tokens[1]);
    const end = tokens.length > 2 ? parseInt(tokens[2]) : start + 1;
    if (isNaN(start) || isNaN(end)) {
        throw Error(`Unparsable bed record: ${tokens.join('\t')}`);
    }
    const feature = {chr: chr, start: start, end: end, score: 1000};

    try {
        if (tokens.length > 3) {

            // Note: these are very special rules for the gencode gene files.
            // tmp = tokens[3].replace(/"/g, '');
            // idName = tmp.split(';');
            // for (var i = 0; i < idName.length; i++) {
            //     var kv = idName[i].split('=');
            //     if (kv[0] == "gene_id") {
            //         id = kv[1];
            //     }
            //     if (kv[0] == "gene_name") {
            //         name = kv[1];
            //     }
            // }
            // feature.id = id ? id : tmp;
            // feature.name = name ? name : tmp;

            //parse gffTags in the name field
            if (tokens[3].indexOf(';') > 0 && tokens[3].indexOf('=') > 0) {
                const attributes = parseAttributeString(tokens[3], '=');
                for (let nmField of gffNameFields) {
                    if (attributes.hasOwnProperty(nmField)) {
                        feature.name = attributes[nmField];
                        delete attributes[nmField];
                        break;
                    }
                }
                feature.attributes = attributes;
            }
            if (!feature.name) {
                feature.name = tokens[3] === '.' ? '' : tokens[3];
            }
        }

        if (tokens.length > 4) {
            feature.score = tokens[4] === '.' ? 0 : parseFloat(tokens[4]);
            if (isNaN(feature.score)) {
                return feature;
            }
        }

        if (tokens.length > 5) {
            feature.strand = tokens[5];
            if (!(feature.strand === '.' || feature.strand === '+' || feature.strand === '-')) {
                return feature;
            }
        }

        if (tokens.length > 6) {
            feature.cdStart = parseInt(tokens[6]);
            if (isNaN(feature.cdStart)) {
                return feature;
            }
        }

        if (tokens.length > 7) {
            feature.cdEnd = parseInt(tokens[7]);
            if (isNaN(feature.cdEnd)) {
                return feature;
            }
        }

        if (tokens.length > 8) {
            if (tokens[8] !== "." && tokens[8] !== "0")
                feature.color = IGVColor.createColorString(tokens[8]);
        }

        if (tokens.length > 11) {
            const exonCount = parseInt(tokens[9]);
            // Some basic validation
            if (exonCount > 1000) {
                // unlikely
                return feature;
            }

            const exonSizes = tokens[10].split(',');
            const exonStarts = tokens[11].split(',');
            if (!(exonSizes.length === exonStarts.length && exonCount === exonSizes.length)) {
                return feature;
            }

            const exons = [];
            for (let i = 0; i < exonCount; i++) {
                const eStart = start + parseInt(exonStarts[i]);
                const eEnd = eStart + parseInt(exonSizes[i]);
                exons.push({start: eStart, end: eEnd});
            }
            findUTRs(exons, feature.cdStart, feature.cdEnd)
            feature.exons = exons;
        }

        // Optional extra columns
        if (header) {
            let thicknessColumn = header.thicknessColumn;
            let colorColumn = header.colorColumn;
            if (colorColumn && colorColumn < tokens.length) {
                feature.color = IGVColor.createColorString(tokens[colorColumn])
            }
            if (thicknessColumn && thicknessColumn < tokens.length) {
                feature.thickness = tokens[thicknessColumn];
            }
        }
    } catch
        (e) {

    }

    return feature;

}

/**
 * Decode a UCSC repeat masker record.
 *
 * Columns, from UCSC documentation
 *
 * 0  bin    585    smallint(5) unsigned    Indexing field to speed chromosome range queries.
 * 1  swScore    1504    int(10) unsigned    Smith Waterman alignment score
 * 2  milliDiv    13    int(10) unsigned    Base mismatches in parts per thousand
 * 3  milliDel    4    int(10) unsigned    Bases deleted in parts per thousand
 * 4  milliIns    13    int(10) unsigned    Bases inserted in parts per thousand
 * 5  genoName    chr1    varchar(255)    Genomic sequence name
 * 6  genoStart    10000    int(10) unsigned    Start in genomic sequence
 * 7  genoEnd    10468    int(10) unsigned    End in genomic sequence
 * 8  genoLeft    -249240153    int(11)    -#bases after match in genomic sequence
 * 9  strand    +    char(1)    Relative orientation + or -
 * 10 repName    (CCCTAA)n    varchar(255)    Name of repeat
 * 11 repClass    Simple_repeat    varchar(255)    Class of repeat
 * 12 repFamily    Simple_repeat    varchar(255)    Family of repeat
 * 13 repStart    1    int(11)    Start (if strand is +) or -#bases after match (if strand is -) in repeat sequence
 * 14 repEnd    463    int(11)    End in repeat sequence
 * 15 repLeft    0    int(11)    -#bases after match (if strand is +) or start (if strand is -) in repeat sequence
 * 16 id    1    char(1)    First digit of id field in RepeatMasker .out file. Best ignored.
 */
function decodeRepeatMasker(tokens, header) {

    if (tokens.length <= 15) return undefined;

    const feature = {
        swScore: Number.parseInt(tokens[1]),
        milliDiv: Number.parseInt(tokens[2]),
        milliDel: Number.parseInt(tokens[3]),
        milliIns: Number.parseInt(tokens[4]),
        chr: tokens[5],
        start: Number.parseInt(tokens[6]),
        end: Number.parseInt(tokens[7]),
        //genoLeft: tokens[8],
        strand: tokens[9],
        repName: tokens[10],
        repClass: tokens[11],
        repFamily: tokens[12],
        repStart: Number.parseInt(tokens[13]),
        repEnd: Number.parseInt(tokens[14]),
        repLeft: Number.parseInt(tokens[15])
    };

    return feature;

}

/**
 * Decode a UCSC "genePred" record.
 *
 * @param tokens
 * @param ignore
 * @returns {*}
 */
function decodeGenePred(tokens, header) {

    var shift = header.shift === undefined ? 0 : 1;

    if (tokens.length <= 9 + shift) return undefined;

    const cdStart = parseInt(tokens[5 + shift])
    const cdEnd = parseInt(tokens[6 + shift])
    var feature = {
            name: tokens[0 + shift],
            chr: tokens[1 + shift],
            strand: tokens[2 + shift],
            start: parseInt(tokens[3 + shift]),
            end: parseInt(tokens[4 + shift]),
            cdStart: cdStart,
            cdEnd: cdEnd,
            id: tokens[0 + shift]
        },
        exonCount = parseInt(tokens[7 + shift]),
        exonStarts = tokens[8 + shift].split(','),
        exonEnds = tokens[9 + shift].split(','),
        exons = [];

    for (let i = 0; i < exonCount; i++) {
        const start = parseInt(exonStarts[i])
        const end = parseInt(exonEnds[i])
        exons.push({start: start, end: end});
    }
    findUTRs(exons, cdStart, cdEnd)

    feature.exons = exons;

    return feature;

}

/**
 * Decode a UCSC "genePredExt" record.  refGene files are in this format.
 *
 * @param tokens
 * @param ignore
 * @returns {*}
 */
function decodeGenePredExt(tokens, header) {

    var shift = header.shift === undefined ? 0 : 1;

    if (tokens.length <= 11 + shift) return undefined;

    const cdStart = parseInt(tokens[5 + shift])
    const cdEnd = parseInt(tokens[6 + shift])
    const feature = {
            name: tokens[11 + shift],
            chr: tokens[1 + shift],
            strand: tokens[2 + shift],
            start: parseInt(tokens[3 + shift]),
            end: parseInt(tokens[4 + shift]),
            cdStart: cdStart,
            cdEnd: cdEnd,
            id: tokens[0 + shift]
        },
        exonCount = parseInt(tokens[7 + shift]),
        exonStarts = tokens[8 + shift].split(','),
        exonEnds = tokens[9 + shift].split(','),
        exons = [];

    for (let i = 0; i < exonCount; i++) {
        const start = parseInt(exonStarts[i])
        const end = parseInt(exonEnds[i])
        exons.push({start: start, end: end});
    }
    findUTRs(exons, cdStart, cdEnd)

    feature.exons = exons;

    return feature;
}

/**
 * Decode a UCSC "refFlat" record
 * @param tokens
 * @param ignore
 * @returns {*}
 */
function decodeReflat(tokens, header) {

    var shift = header.shift === undefined ? 0 : 1;

    if (tokens.length <= 10 + shift) return undefined;

    const cdStart = parseInt(tokens[6 + shift])
    const cdEnd = parseInt(tokens[7 + shift])
    var feature = {
            name: tokens[0 + shift],
            id: tokens[1 + shift],
            chr: tokens[2 + shift],
            strand: tokens[3 + shift],
            start: parseInt(tokens[4 + shift]),
            end: parseInt(tokens[5 + shift]),
            cdStart: cdStart,
            cdEnd: cdEnd
        },
        exonCount = parseInt(tokens[8 + shift]),
        exonStarts = tokens[9 + shift].split(','),
        exonEnds = tokens[10 + shift].split(','),
        exons = [];

    for (let i = 0; i < exonCount; i++) {
        const start = parseInt(exonStarts[i])
        const end = parseInt(exonEnds[i])
        exons.push({start: start, end: end});
    }
    findUTRs(exons, cdStart, cdEnd)

    feature.exons = exons;

    return feature;
}

function findUTRs(exons, cdStart, cdEnd) {

    for (let exon of exons) {
        const end = exon.end
        const start = exon.start
        if (end < cdStart || start > cdEnd) {
            exon.utr = true;
        } else {
            if (cdStart >= start && cdStart <= end) {
                exon.cdStart = cdStart
            }
            if (cdEnd >= start && cdEnd <= end) {
                exon.cdEnd = cdEnd
            }
        }
    }

}

function decodePeak(tokens, header) {

    var tokenCount, chr, start, end, strand, name, score, qValue, signal, pValue;

    tokenCount = tokens.length;
    if (tokenCount < 9) {
        return undefined;
    }

    chr = tokens[0];
    start = parseInt(tokens[1]);
    end = parseInt(tokens[2]);
    name = tokens[3];
    score = parseFloat(tokens[4]);
    strand = tokens[5].trim();
    signal = parseFloat(tokens[6]);
    pValue = parseFloat(tokens[7]);
    qValue = parseFloat(tokens[8]);

    if (score === 0) score = signal;

    return {
        chr: chr, start: start, end: end, name: name, score: score, strand: strand, signal: signal,
        pValue: pValue, qValue: qValue
    };
}

function decodeBedGraph(tokens, header) {

    var chr, start, end, value;

    if (tokens.length <= 3) return undefined;

    chr = tokens[0];
    start = parseInt(tokens[1]);
    end = parseInt(tokens[2]);
    value = parseFloat(tokens[3]);
    const feature = {chr: chr, start: start, end: end, value: value};

    // Optional extra columns
    if (header) {
        let colorColumn = header.colorColumn;
        if (colorColumn && colorColumn < tokens.length) {
            feature.color = IGVColor.createColorString(tokens[colorColumn])
        }
    }

    return feature;
}

function decodeWig(tokens, header) {

    const wig = header.wig;

    if (wig && wig.format === "fixedStep") {
        const ss = (wig.index * wig.step) + wig.start;
        const ee = ss + wig.span;
        const value = parseFloat(tokens[0]);
        ++(wig.index);
        return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};
    } else if (wig && wig.format === "variableStep") {

        if (tokens.length < 2) return null;
        const ss = parseInt(tokens[0], 10) - 1;
        const ee = ss + wig.span;
        const value = parseFloat(tokens[1]);
        return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};

    } else {
        return decodeBedGraph(tokens);
    }
}

function decodeSNP(tokens, header) {

    if (tokens.length < 6) return undefined;

    const autoSql = [
        'bin',
        'chr',
        'start',
        'end',
        'name',
        'score',
        'strand',
        'refNCBI',
        'refUCSC',
        'observed',
        'molType',
        'class',
        'valid',
        'avHet',
        'avHetSE',
        'func',
        'locType',
        'weight',
        'exceptions',
        'submitterCount',
        'submitters',
        'alleleFreqCount',
        'alleles',
        'alleleNs',
        'alleleFreqs',
        'bitfields'
    ];

    const feature = {
        chr: tokens[1],
        start: Number.parseInt(tokens[2]),
        end: Number.parseInt(tokens[3]),
        name: tokens[4],
        score: Number.parseInt(tokens[5])
    };

    const n = Math.min(tokens.length, autoSql.length);
    for (let i = 6; i < n; i++) {
        feature[autoSql[i]] = tokens[i];
    }
    return feature;

}

export {
    decodeBed, decodeBedGraph, decodeGenePred, decodeGenePredExt, decodePeak, decodeReflat, decodeRepeatMasker,
    decodeSNP, decodeWig
}

<commit_msg>UCSC exon parsing bug.  Fixes #1222<commit_after>import {IGVColor} from "../../../node_modules/igv-utils/src/index.js";

import {gffNameFields, parseAttributeString} from "./gff.js";

/**
 * Decode the UCSC bed format.  Only the first 3 columns (chr, start, end) are required.   The remaining columns
 * must follow standard bed order, but we will tolerate deviations after column 3.
 *
 * @param tokens
 * @param ignore
 * @returns decoded feature, or null if this is not a valid record
 */
function decodeBed(tokens, header) {

    if (tokens.length < 3) return undefined;

    const gffTags = header && header.gffTags;

    const chr = tokens[0];
    const start = parseInt(tokens[1]);
    const end = tokens.length > 2 ? parseInt(tokens[2]) : start + 1;
    if (isNaN(start) || isNaN(end)) {
        throw Error(`Unparsable bed record: ${tokens.join('\t')}`);
    }
    const feature = {chr: chr, start: start, end: end, score: 1000};

    try {
        if (tokens.length > 3) {

            // Note: these are very special rules for the gencode gene files.
            // tmp = tokens[3].replace(/"/g, '');
            // idName = tmp.split(';');
            // for (var i = 0; i < idName.length; i++) {
            //     var kv = idName[i].split('=');
            //     if (kv[0] == "gene_id") {
            //         id = kv[1];
            //     }
            //     if (kv[0] == "gene_name") {
            //         name = kv[1];
            //     }
            // }
            // feature.id = id ? id : tmp;
            // feature.name = name ? name : tmp;

            //parse gffTags in the name field
            if (tokens[3].indexOf(';') > 0 && tokens[3].indexOf('=') > 0) {
                const attributes = parseAttributeString(tokens[3], '=');
                for (let nmField of gffNameFields) {
                    if (attributes.hasOwnProperty(nmField)) {
                        feature.name = attributes[nmField];
                        delete attributes[nmField];
                        break;
                    }
                }
                feature.attributes = attributes;
            }
            if (!feature.name) {
                feature.name = tokens[3] === '.' ? '' : tokens[3];
            }
        }

        if (tokens.length > 4) {
            feature.score = tokens[4] === '.' ? 0 : parseFloat(tokens[4]);
            if (isNaN(feature.score)) {
                return feature;
            }
        }

        if (tokens.length > 5) {
            feature.strand = tokens[5];
            if (!(feature.strand === '.' || feature.strand === '+' || feature.strand === '-')) {
                return feature;
            }
        }

        if (tokens.length > 6) {
            feature.cdStart = parseInt(tokens[6]);
            if (isNaN(feature.cdStart)) {
                return feature;
            }
        }

        if (tokens.length > 7) {
            feature.cdEnd = parseInt(tokens[7]);
            if (isNaN(feature.cdEnd)) {
                return feature;
            }
        }

        if (tokens.length > 8) {
            if (tokens[8] !== "." && tokens[8] !== "0")
                feature.color = IGVColor.createColorString(tokens[8]);
        }

        if (tokens.length > 11) {
            const exonCount = parseInt(tokens[9]);
            // Some basic validation
            if (exonCount > 1000) {
                // unlikely
                return feature;
            }

            const exonSizes = tokens[10].replace(/,$/, '').split(',');
            const exonStarts = tokens[11].replace(/,$/, '').split(',');
            if (!(exonSizes.length === exonStarts.length && exonCount === exonSizes.length)) {
                return feature;
            }

            const exons = [];
            for (let i = 0; i < exonCount; i++) {
                const eStart = start + parseInt(exonStarts[i]);
                const eEnd = eStart + parseInt(exonSizes[i]);
                exons.push({start: eStart, end: eEnd});
            }
            findUTRs(exons, feature.cdStart, feature.cdEnd)
            feature.exons = exons;
        }

        // Optional extra columns
        if (header) {
            let thicknessColumn = header.thicknessColumn;
            let colorColumn = header.colorColumn;
            if (colorColumn && colorColumn < tokens.length) {
                feature.color = IGVColor.createColorString(tokens[colorColumn])
            }
            if (thicknessColumn && thicknessColumn < tokens.length) {
                feature.thickness = tokens[thicknessColumn];
            }
        }
    } catch
        (e) {

    }

    return feature;

}

/**
 * Decode a UCSC repeat masker record.
 *
 * Columns, from UCSC documentation
 *
 * 0  bin    585    smallint(5) unsigned    Indexing field to speed chromosome range queries.
 * 1  swScore    1504    int(10) unsigned    Smith Waterman alignment score
 * 2  milliDiv    13    int(10) unsigned    Base mismatches in parts per thousand
 * 3  milliDel    4    int(10) unsigned    Bases deleted in parts per thousand
 * 4  milliIns    13    int(10) unsigned    Bases inserted in parts per thousand
 * 5  genoName    chr1    varchar(255)    Genomic sequence name
 * 6  genoStart    10000    int(10) unsigned    Start in genomic sequence
 * 7  genoEnd    10468    int(10) unsigned    End in genomic sequence
 * 8  genoLeft    -249240153    int(11)    -#bases after match in genomic sequence
 * 9  strand    +    char(1)    Relative orientation + or -
 * 10 repName    (CCCTAA)n    varchar(255)    Name of repeat
 * 11 repClass    Simple_repeat    varchar(255)    Class of repeat
 * 12 repFamily    Simple_repeat    varchar(255)    Family of repeat
 * 13 repStart    1    int(11)    Start (if strand is +) or -#bases after match (if strand is -) in repeat sequence
 * 14 repEnd    463    int(11)    End in repeat sequence
 * 15 repLeft    0    int(11)    -#bases after match (if strand is +) or start (if strand is -) in repeat sequence
 * 16 id    1    char(1)    First digit of id field in RepeatMasker .out file. Best ignored.
 */
function decodeRepeatMasker(tokens, header) {

    if (tokens.length <= 15) return undefined;

    const feature = {
        swScore: Number.parseInt(tokens[1]),
        milliDiv: Number.parseInt(tokens[2]),
        milliDel: Number.parseInt(tokens[3]),
        milliIns: Number.parseInt(tokens[4]),
        chr: tokens[5],
        start: Number.parseInt(tokens[6]),
        end: Number.parseInt(tokens[7]),
        //genoLeft: tokens[8],
        strand: tokens[9],
        repName: tokens[10],
        repClass: tokens[11],
        repFamily: tokens[12],
        repStart: Number.parseInt(tokens[13]),
        repEnd: Number.parseInt(tokens[14]),
        repLeft: Number.parseInt(tokens[15])
    };

    return feature;

}

/**
 * Decode a UCSC "genePred" record.
 *
 * @param tokens
 * @param ignore
 * @returns {*}
 */
function decodeGenePred(tokens, header) {

    var shift = header.shift === undefined ? 0 : 1;

    if (tokens.length <= 9 + shift) return undefined;

    const cdStart = parseInt(tokens[5 + shift])
    const cdEnd = parseInt(tokens[6 + shift])
    var feature = {
            name: tokens[0 + shift],
            chr: tokens[1 + shift],
            strand: tokens[2 + shift],
            start: parseInt(tokens[3 + shift]),
            end: parseInt(tokens[4 + shift]),
            cdStart: cdStart,
            cdEnd: cdEnd,
            id: tokens[0 + shift]
        },
        exonCount = parseInt(tokens[7 + shift]),
        exonStarts = tokens[8 + shift].split(','),
        exonEnds = tokens[9 + shift].split(','),
        exons = [];

    for (let i = 0; i < exonCount; i++) {
        const start = parseInt(exonStarts[i])
        const end = parseInt(exonEnds[i])
        exons.push({start: start, end: end});
    }
    findUTRs(exons, cdStart, cdEnd)

    feature.exons = exons;

    return feature;

}

/**
 * Decode a UCSC "genePredExt" record.  refGene files are in this format.
 *
 * @param tokens
 * @param ignore
 * @returns {*}
 */
function decodeGenePredExt(tokens, header) {

    var shift = header.shift === undefined ? 0 : 1;

    if (tokens.length <= 11 + shift) return undefined;

    const cdStart = parseInt(tokens[5 + shift])
    const cdEnd = parseInt(tokens[6 + shift])
    const feature = {
            name: tokens[11 + shift],
            chr: tokens[1 + shift],
            strand: tokens[2 + shift],
            start: parseInt(tokens[3 + shift]),
            end: parseInt(tokens[4 + shift]),
            cdStart: cdStart,
            cdEnd: cdEnd,
            id: tokens[0 + shift]
        },
        exonCount = parseInt(tokens[7 + shift]),
        exonStarts = tokens[8 + shift].split(','),
        exonEnds = tokens[9 + shift].split(','),
        exons = [];

    for (let i = 0; i < exonCount; i++) {
        const start = parseInt(exonStarts[i])
        const end = parseInt(exonEnds[i])
        exons.push({start: start, end: end});
    }
    findUTRs(exons, cdStart, cdEnd)

    feature.exons = exons;

    return feature;
}

/**
 * Decode a UCSC "refFlat" record
 * @param tokens
 * @param ignore
 * @returns {*}
 */
function decodeReflat(tokens, header) {

    var shift = header.shift === undefined ? 0 : 1;

    if (tokens.length <= 10 + shift) return undefined;

    const cdStart = parseInt(tokens[6 + shift])
    const cdEnd = parseInt(tokens[7 + shift])
    var feature = {
            name: tokens[0 + shift],
            id: tokens[1 + shift],
            chr: tokens[2 + shift],
            strand: tokens[3 + shift],
            start: parseInt(tokens[4 + shift]),
            end: parseInt(tokens[5 + shift]),
            cdStart: cdStart,
            cdEnd: cdEnd
        },
        exonCount = parseInt(tokens[8 + shift]),
        exonStarts = tokens[9 + shift].split(','),
        exonEnds = tokens[10 + shift].split(','),
        exons = [];

    for (let i = 0; i < exonCount; i++) {
        const start = parseInt(exonStarts[i])
        const end = parseInt(exonEnds[i])
        exons.push({start: start, end: end});
    }
    findUTRs(exons, cdStart, cdEnd)

    feature.exons = exons;

    return feature;
}

function findUTRs(exons, cdStart, cdEnd) {

    for (let exon of exons) {
        const end = exon.end
        const start = exon.start
        if (end < cdStart || start > cdEnd) {
            exon.utr = true;
        } else {
            if (cdStart >= start && cdStart <= end) {
                exon.cdStart = cdStart
            }
            if (cdEnd >= start && cdEnd <= end) {
                exon.cdEnd = cdEnd
            }
        }
    }

}

function decodePeak(tokens, header) {

    var tokenCount, chr, start, end, strand, name, score, qValue, signal, pValue;

    tokenCount = tokens.length;
    if (tokenCount < 9) {
        return undefined;
    }

    chr = tokens[0];
    start = parseInt(tokens[1]);
    end = parseInt(tokens[2]);
    name = tokens[3];
    score = parseFloat(tokens[4]);
    strand = tokens[5].trim();
    signal = parseFloat(tokens[6]);
    pValue = parseFloat(tokens[7]);
    qValue = parseFloat(tokens[8]);

    if (score === 0) score = signal;

    return {
        chr: chr, start: start, end: end, name: name, score: score, strand: strand, signal: signal,
        pValue: pValue, qValue: qValue
    };
}

function decodeBedGraph(tokens, header) {

    var chr, start, end, value;

    if (tokens.length <= 3) return undefined;

    chr = tokens[0];
    start = parseInt(tokens[1]);
    end = parseInt(tokens[2]);
    value = parseFloat(tokens[3]);
    const feature = {chr: chr, start: start, end: end, value: value};

    // Optional extra columns
    if (header) {
        let colorColumn = header.colorColumn;
        if (colorColumn && colorColumn < tokens.length) {
            feature.color = IGVColor.createColorString(tokens[colorColumn])
        }
    }

    return feature;
}

function decodeWig(tokens, header) {

    const wig = header.wig;

    if (wig && wig.format === "fixedStep") {
        const ss = (wig.index * wig.step) + wig.start;
        const ee = ss + wig.span;
        const value = parseFloat(tokens[0]);
        ++(wig.index);
        return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};
    } else if (wig && wig.format === "variableStep") {

        if (tokens.length < 2) return null;
        const ss = parseInt(tokens[0], 10) - 1;
        const ee = ss + wig.span;
        const value = parseFloat(tokens[1]);
        return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};

    } else {
        return decodeBedGraph(tokens);
    }
}

function decodeSNP(tokens, header) {

    if (tokens.length < 6) return undefined;

    const autoSql = [
        'bin',
        'chr',
        'start',
        'end',
        'name',
        'score',
        'strand',
        'refNCBI',
        'refUCSC',
        'observed',
        'molType',
        'class',
        'valid',
        'avHet',
        'avHetSE',
        'func',
        'locType',
        'weight',
        'exceptions',
        'submitterCount',
        'submitters',
        'alleleFreqCount',
        'alleles',
        'alleleNs',
        'alleleFreqs',
        'bitfields'
    ];

    const feature = {
        chr: tokens[1],
        start: Number.parseInt(tokens[2]),
        end: Number.parseInt(tokens[3]),
        name: tokens[4],
        score: Number.parseInt(tokens[5])
    };

    const n = Math.min(tokens.length, autoSql.length);
    for (let i = 6; i < n; i++) {
        feature[autoSql[i]] = tokens[i];
    }
    return feature;

}

export {
    decodeBed, decodeBedGraph, decodeGenePred, decodeGenePredExt, decodePeak, decodeReflat, decodeRepeatMasker,
    decodeSNP, decodeWig
}

<|endoftext|>


<commit_before>/**
* Native embed library:
*
* Enables embedPlayer support for native iOS/Android webView playback system
*/

( function( mw, $ ) { "use strict";
    //make the player transparent to see the native iOS/Android player
    if(mw.getConfig( "EmbedPlayer.ForceNativeComponent")){
        $('body,.videoHolder').css('background-color', 'transparent' );
    }

mw.EmbedPlayerNativeComponent = {
	//Instance Name
	instanceOf: 'NativeComponent',

	// Flag to only load the video ( not play it )
	onlyLoadFlag:false,

	//Callback fired once video is "loaded"
	onLoadedCallback: null,

	// The previous "currentTime" to sniff seek actions
	// NOTE the bug where onSeeked does not seem fire consistently may no longer be applicable
	prevCurrentTime: -1,

	// Store the progress event ( updated during monitor )
	progressEventData: null,

	// If the media loaded event has been fired
	mediaLoadedFlag: null,

	// A flag to keep the video tag offscreen.
	keepPlayerOffScreenFlag: null,

	// A flag to designate the first play event, as to not propagate the native event in this case
	ignoreNextNativeEvent: null,

	// A local var to store the current seek target time:
	currentSeekTargetTime: null,

	// All the native events per:
	// http://www.w3.org/TR/html5/video.html#mediaevents
	nativeEvents : [
        'stop',
		'play',
		'pause',
		'canplay',
		'seeking',
		'seeked',
		'ended',
        'error',
        'stalled',
        'loadedmetadata',
        'timeupdate',
        'progress',
        'enterfullscreen',
        'exitfullscreen'
	],
	// Native player supported feature set
	supports: {
		'playHead' : true,
		'pause' : true,
		'fullscreen' : true,
		'SourceSelector': false,
		'timeDisplay' : true,
		'volumeControl' : false,
		'overlays' : true
	},

    embedPlayerHTML : function() {
        //removing video tag

        var _this = this;
        if ( !this.playerIsLoaded ){
            mw.log( "NativeComponent:: embedPlayerHTML" );
            // remove any existing pid ( if present )
            $( '#' + this.pid ).remove();

            var divElement = document.createElement("div");
            divElement.setAttribute('id', 'proxy');
            document.body.appendChild(divElement);

            this.proxyElement = divElement;
            try{
                if(NativeBridge.videoPlayer){
                    NativeBridge.videoPlayer.registePlayer(this.getPlayerElement());
                }
            }
            catch(e){
                alert( e );
            }

            this.applyMediaElementBindings();
            this.getPlayerElement().attr('src', this.getSrc());
            this.playerIsLoaded = true;
        }
    },

	/**
	 * Apply media element bindings
	 */
	applyMediaElementBindings: function() {
		var _this = this;
		mw.log("EmbedPlayerNative::MediaElementBindings");

		$.each( _this.nativeEvents, function( inx, eventName ){
			$( _this.getPlayerElement() ).unbind( eventName ).bind( eventName , function(){
				// make sure we propagating events, and the current instance is in the correct closure.
				if( _this._propagateEvents && _this.instanceOf == 'NativeComponent' ){
					var argArray = $.makeArray( arguments );
					// Check if there is local handler:

					if( _this[ '_on' + eventName ] ){
						_this[ '_on' + eventName ].apply( _this, argArray);
					} else {
						// No local handler directly propagate the event to the abstract object:
						$( _this ).trigger( eventName, argArray );
					}
				}
			});
		});
	},

    /**
     * Get the embed player time
     */
    getPlayerElementTime: function() {
        var _this = this;
        // Make sure we have .vid obj

        if ( !this.getPlayerElement() ) {
            mw.log( 'EmbedPlayerNative::getPlayerElementTime: ' + this.id + ' not in dom ( stop monitor)' );
            this.stop();
            return false;
        }
        var ct =  this.getPlayerElement().attr('currentTime');
        // Return 0 or a positive number:
        if( ! ct || isNaN( ct ) || ct < 0 || ! isFinite( ct ) ){
            return 0;
        }
        // Return the playerElement currentTime
        return  ct ;
    },

    /**
     * Get /update the playerElement value
     */
    getPlayerElement: function () {
        return this.proxyElement;
    },

    /**
     * Stop the player ( end all listeners )
     */
    stop: function(){
        this.parent_stop();
        if( this.getPlayerElement() && this.getPlayerElement().attr('currentTime')){
            this.getPlayerElement().attr('currentTime', '0');
            this.getPlayerElement().stop();
        }
    },

    /**
     * Play back the video stream
     * calls parent_play to update the interface
     */

    play: function() {
        $( this ).find( '.playerPoster' ).remove();

        if ( this.getPlayerElement() ) { // update player
            this.getPlayerElement().play();
        }
        $( this ).trigger( "playing" );

        if( this.parent_play() ){
            this.monitor();
        }
    },

    /**
	* Pause the video playback
	* calls parent_pause to update the interface
	*/
	pause: function() {
		this.parent_pause(); // update interface
		if ( this.getPlayerElement() ) { // update player
            this.getPlayerElement().pause();
		}
	},

	seek: function( percentage ) {
		var seekTime = percentage * this.getDuration();
		this.getPlayerElement().attr('currentTime', seekTime);
		this.parent_seek( percentage );
	},

    /**
     * Handle the native play event
     */
    _onplay: function(){
        mw.log("EmbedPlayerNativeComponent:: OnPlay::");

        this.updatePlayhead();
        $( this ).trigger( "playing" );

        if( this.paused  && this.parent_play() ){
            this.monitor();
        }
    },

	/**
	* Handle the native paused event
	*/
    /**
     * Handle the native paused event
     */
    _onpause: function(){
        mw.log("EmbedPlayerNativeComponent:: OnPause::");
        this.parent_pause();
    },

    /**
     * Local method for seeking event
     * fired when "seeking"
     */
    _onseeking: function() {
        mw.log( "EmbedPlayerNative::onSeeking " );

        // Trigger the html5 "seeking" trigger
        mw.log("EmbedPlayerNative::seeking:trigger:: ");
        if( this._propagateEvents ){
            this.triggerHelper( 'seeking' );
        }
    },

    /**
     * Local method for seeked event
     * fired when done seeking
     */
    _onseeked: function() {
        mw.log("EmbedPlayerNative::onSeeked " );

        if( this._propagateEvents ){
            mw.log( "EmbedPlayerNative:: trigger: seeked" );
            this.triggerHelper( 'seeked' );
        }

        this.monitor();
    },

    /**
     * Local method for metadata ready
     * fired when metadata becomes available
     *
     * Used to update the media duration to
     * accurately reflect the src duration
     */
    _onloadedmetadata: function() {
        //Fire "onLoaded" flags if set
        if( typeof this.onLoadedCallback == 'function' ) {
            this.onLoadedCallback();
        }

        // Trigger "media loaded"
        if( ! this.mediaLoadedFlag ){
            $( this ).trigger( 'mediaLoaded' );
            this.mediaLoadedFlag = true;
        }
    },

    /**
     * Local method for end of media event
     */
    _onended: function( event ) {
        if( this.getPlayerElement() ){
            mw.log( 'EmbedPlayer:native: onended:');
            if( this._propagateEvents ){
                this.onClipDone();
            }
        }
    },

    /**
     * Local onClip done function for native player.
     */
    onClipDone: function(){
        this.parent_onClipDone();
    },

    /**
     * playback error
     */
    _onerror: function ( event ) {
        this.triggerHelper( 'embedPlayerError' );
    },

	/**
	 * buffer progress
	 * @param event
	 * @param progress
	 * @private
	 */
	_onprogress: function( event , progress ) {
		if ( typeof progress !== 'undefined' ) {
			this.updateBufferStatus( progress );
		}
	},

	/*
	 * Write the Embed html to the target
	 */
    getVideoElementPosition: function(){
        var videoElementDiv = parent.document.getElementById( this.id );
        var videoElementRect = videoElementDiv.getBoundingClientRect();

        return videoElementRect;
    },

    drawVideoNativeComponent: function(){
        var videoElementPosition = this.getVideoElementPosition();
        var x = videoElementPosition.left;
        var y = videoElementPosition.top;
        var w = videoElementPosition.right - videoElementPosition.left;
        var h = videoElementPosition.bottom - videoElementPosition.top;

        this.getPlayerElement().drawVideoNativeComponent( [x, y, w, h] );
    },

    showNativePlayer: function(){
        this.getPlayerElement().showNativePlayer();
    },

    hideNativePlayer: function(){
        this.getPlayerElement().hideNativePlayer();
    },

    useNativePlayerControls: function() {
        return false;
    },

    /**
     * Passes a fullscreen request to the layoutBuilder interface
     */
	toggleFullscreen: function() {
        this.getPlayerElement().toggleFullscreen();
    }
};
} )( mediaWiki, jQuery );

<commit_msg>cleaning wrong comment<commit_after>/**
* Native embed library:
*
* Enables embedPlayer support for native iOS/Android webView playback system
*/

( function( mw, $ ) { "use strict";
    //make the player transparent to see the native iOS/Android player
    if(mw.getConfig( "EmbedPlayer.ForceNativeComponent")){
        $('body,.videoHolder').css('background-color', 'transparent' );
    }

mw.EmbedPlayerNativeComponent = {
	//Instance Name
	instanceOf: 'NativeComponent',

	// Flag to only load the video ( not play it )
	onlyLoadFlag:false,

	//Callback fired once video is "loaded"
	onLoadedCallback: null,

	// The previous "currentTime" to sniff seek actions
	// NOTE the bug where onSeeked does not seem fire consistently may no longer be applicable
	prevCurrentTime: -1,

	// Store the progress event ( updated during monitor )
	progressEventData: null,

	// If the media loaded event has been fired
	mediaLoadedFlag: null,

	// A flag to keep the video tag offscreen.
	keepPlayerOffScreenFlag: null,

	// A flag to designate the first play event, as to not propagate the native event in this case
	ignoreNextNativeEvent: null,

	// A local var to store the current seek target time:
	currentSeekTargetTime: null,

	// All the native events per:
	// http://www.w3.org/TR/html5/video.html#mediaevents
	nativeEvents : [
        'stop',
		'play',
		'pause',
		'canplay',
		'seeking',
		'seeked',
		'ended',
        'error',
        'stalled',
        'loadedmetadata',
        'timeupdate',
        'progress',
        'enterfullscreen',
        'exitfullscreen'
	],
	// Native player supported feature set
	supports: {
		'playHead' : true,
		'pause' : true,
		'fullscreen' : true,
		'SourceSelector': false,
		'timeDisplay' : true,
		'volumeControl' : false,
		'overlays' : true
	},

    embedPlayerHTML : function() {
        var _this = this;
        if ( !this.playerIsLoaded ){
            mw.log( "NativeComponent:: embedPlayerHTML" );
            // remove any existing pid ( if present )
            $( '#' + this.pid ).remove();

            var divElement = document.createElement("div");
            divElement.setAttribute('id', 'proxy');
            document.body.appendChild(divElement);

            this.proxyElement = divElement;
            try{
                if(NativeBridge.videoPlayer){
                    NativeBridge.videoPlayer.registePlayer(this.getPlayerElement());
                }
            }
            catch(e){
                alert( e );
            }

            this.applyMediaElementBindings();
            this.getPlayerElement().attr('src', this.getSrc());
            this.playerIsLoaded = true;
        }
    },

	/**
	 * Apply media element bindings
	 */
	applyMediaElementBindings: function() {
		var _this = this;
		mw.log("EmbedPlayerNative::MediaElementBindings");

		$.each( _this.nativeEvents, function( inx, eventName ){
			$( _this.getPlayerElement() ).unbind( eventName ).bind( eventName , function(){
				// make sure we propagating events, and the current instance is in the correct closure.
				if( _this._propagateEvents && _this.instanceOf == 'NativeComponent' ){
					var argArray = $.makeArray( arguments );
					// Check if there is local handler:

					if( _this[ '_on' + eventName ] ){
						_this[ '_on' + eventName ].apply( _this, argArray);
					} else {
						// No local handler directly propagate the event to the abstract object:
						$( _this ).trigger( eventName, argArray );
					}
				}
			});
		});
	},

    /**
     * Get the embed player time
     */
    getPlayerElementTime: function() {
        var _this = this;
        // Make sure we have .vid obj

        if ( !this.getPlayerElement() ) {
            mw.log( 'EmbedPlayerNative::getPlayerElementTime: ' + this.id + ' not in dom ( stop monitor)' );
            this.stop();
            return false;
        }
        var ct =  this.getPlayerElement().attr('currentTime');
        // Return 0 or a positive number:
        if( ! ct || isNaN( ct ) || ct < 0 || ! isFinite( ct ) ){
            return 0;
        }
        // Return the playerElement currentTime
        return  ct ;
    },

    /**
     * Get /update the playerElement value
     */
    getPlayerElement: function () {
        return this.proxyElement;
    },

    /**
     * Stop the player ( end all listeners )
     */
    stop: function(){
        this.parent_stop();
        if( this.getPlayerElement() && this.getPlayerElement().attr('currentTime')){
            this.getPlayerElement().attr('currentTime', '0');
            this.getPlayerElement().stop();
        }
    },

    /**
     * Play back the video stream
     * calls parent_play to update the interface
     */

    play: function() {
        $( this ).find( '.playerPoster' ).remove();

        if ( this.getPlayerElement() ) { // update player
            this.getPlayerElement().play();
        }
        $( this ).trigger( "playing" );

        if( this.parent_play() ){
            this.monitor();
        }
    },

    /**
	* Pause the video playback
	* calls parent_pause to update the interface
	*/
	pause: function() {
		this.parent_pause(); // update interface
		if ( this.getPlayerElement() ) { // update player
            this.getPlayerElement().pause();
		}
	},

	seek: function( percentage ) {
		var seekTime = percentage * this.getDuration();
		this.getPlayerElement().attr('currentTime', seekTime);
		this.parent_seek( percentage );
	},

    /**
     * Handle the native play event
     */
    _onplay: function(){
        mw.log("EmbedPlayerNativeComponent:: OnPlay::");

        this.updatePlayhead();
        $( this ).trigger( "playing" );

        if( this.paused  && this.parent_play() ){
            this.monitor();
        }
    },

	/**
	* Handle the native paused event
	*/
    /**
     * Handle the native paused event
     */
    _onpause: function(){
        mw.log("EmbedPlayerNativeComponent:: OnPause::");
        this.parent_pause();
    },

    /**
     * Local method for seeking event
     * fired when "seeking"
     */
    _onseeking: function() {
        mw.log( "EmbedPlayerNative::onSeeking " );

        // Trigger the html5 "seeking" trigger
        mw.log("EmbedPlayerNative::seeking:trigger:: ");
        if( this._propagateEvents ){
            this.triggerHelper( 'seeking' );
        }
    },

    /**
     * Local method for seeked event
     * fired when done seeking
     */
    _onseeked: function() {
        mw.log("EmbedPlayerNative::onSeeked " );

        if( this._propagateEvents ){
            mw.log( "EmbedPlayerNative:: trigger: seeked" );
            this.triggerHelper( 'seeked' );
        }

        this.monitor();
    },

    /**
     * Local method for metadata ready
     * fired when metadata becomes available
     *
     * Used to update the media duration to
     * accurately reflect the src duration
     */
    _onloadedmetadata: function() {
        //Fire "onLoaded" flags if set
        if( typeof this.onLoadedCallback == 'function' ) {
            this.onLoadedCallback();
        }

        // Trigger "media loaded"
        if( ! this.mediaLoadedFlag ){
            $( this ).trigger( 'mediaLoaded' );
            this.mediaLoadedFlag = true;
        }
    },

    /**
     * Local method for end of media event
     */
    _onended: function( event ) {
        if( this.getPlayerElement() ){
            mw.log( 'EmbedPlayer:native: onended:');
            if( this._propagateEvents ){
                this.onClipDone();
            }
        }
    },

    /**
     * Local onClip done function for native player.
     */
    onClipDone: function(){
        this.parent_onClipDone();
    },

    /**
     * playback error
     */
    _onerror: function ( event ) {
        this.triggerHelper( 'embedPlayerError' );
    },

	/**
	 * buffer progress
	 * @param event
	 * @param progress
	 * @private
	 */
	_onprogress: function( event , progress ) {
		if ( typeof progress !== 'undefined' ) {
			this.updateBufferStatus( progress );
		}
	},

	/*
	 * Write the Embed html to the target
	 */
    getVideoElementPosition: function(){
        var videoElementDiv = parent.document.getElementById( this.id );
        var videoElementRect = videoElementDiv.getBoundingClientRect();

        return videoElementRect;
    },

    drawVideoNativeComponent: function(){
        var videoElementPosition = this.getVideoElementPosition();
        var x = videoElementPosition.left;
        var y = videoElementPosition.top;
        var w = videoElementPosition.right - videoElementPosition.left;
        var h = videoElementPosition.bottom - videoElementPosition.top;

        this.getPlayerElement().drawVideoNativeComponent( [x, y, w, h] );
    },

    showNativePlayer: function(){
        this.getPlayerElement().showNativePlayer();
    },

    hideNativePlayer: function(){
        this.getPlayerElement().hideNativePlayer();
    },

    useNativePlayerControls: function() {
        return false;
    },

    /**
     * Passes a fullscreen request to the layoutBuilder interface
     */
	toggleFullscreen: function() {
        this.getPlayerElement().toggleFullscreen();
    }
};
} )( mediaWiki, jQuery );

<|endoftext|>


<commit_before>var Kroll = {
	moduleInfo: {},
	externalChildModules: {},

	addModuleInfo: function(info) {
		customModuleInfo[info.id] = info;
	},

	getModuleInfo: function(id) {
		return customModuleInfo[id];
	},

	getCustomModuleIds: function() {
		return Object.keys(customModuleInfo);
	}
};<commit_msg>load native module support from main js file <commit_after>(function(self) {
  global = this;

  function startup() {
  }

  var runInThisContext = self.binding('evals').Script.runInThisContext;

  function NativeModule(id) {
    this.filename = id + '.js';
    this.id = id;
    this.exports = {};
    this.loaded = false;
  }

  NativeModule._source = self.binding('natives');
  NativeModule._cache = {};

  NativeModule.require = function(id) {
    if (id == 'native_module') {
      return NativeModule;
    }

    var cached = NativeModule.getCached(id);
    if (cached) {
      return cached.exports;
    }

    if (!NativeModule.exists(id)) {
      throw new Error('No such native module ' + id);
    }

    var nativeModule = new NativeModule(id);

    nativeModule.compile();
    nativeModule.cache();

    return nativeModule.exports;
  };

  NativeModule.getCached = function(id) {
    return NativeModule._cache[id];
  }

  NativeModule.exists = function(id) {
    return (id in NativeModule._source);
  }

  NativeModule.getSource = function(id) {
    return NativeModule._source[id];
  }

  NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

  NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
  ];

  NativeModule.prototype.compile = function() {
    var source = NativeModule.getSource(this.id);
    source = NativeModule.wrap(source);

    var fn = runInThisContext(source, this.filename, true);
    fn(this.exports, NativeModule.require, this, this.filename);
    this.loaded = true;
  };

  NativeModule.prototype.cache = function() {
    NativeModule._cache[this.id] = this;
  };

  startup();
});
<|endoftext|>


<commit_before>// jquery.tweet.js - See http://tweet.seaofclouds.com/ or https://github.com/seaofclouds/tweet for more info
// Copyright (c) 2008-2011 Todd Matthews & Steve Purcell
(function (factory) {
  if (typeof define === 'function' && define.amd)
    define(['jquery'], factory); // AMD support for RequireJS etc.
  else
    factory(jQuery);
}(function ($) {
  $.fn.tweet = function(o){
    var s = $.extend({
      username: null,                           // [string or array] required unless using the 'query' option; one or more twitter screen names (use 'list' option for multiple names, where possible)
      list: null,                               // [string]   optional name of list belonging to username
      favorites: false,                         // [boolean]  display the user's favorites instead of his tweets
      query: null,                              // [string]   optional search query (see also: http://search.twitter.com/operators)
      avatar_size: null,                        // [integer]  height and width of avatar if displayed (48px max)
      count: 3,                                 // [integer]  how many tweets to display?
      fetch: null,                              // [integer]  how many tweets to fetch via the API (set this higher than 'count' if using the 'filter' option)
      page: 1,                                  // [integer]  which page of results to fetch (if count != fetch, you'll get unexpected results)
      retweets: true,                           // [boolean]  whether to fetch (official) retweets (not supported in all display modes)
      intro_text: null,                         // [string]   do you want text BEFORE your your tweets?
      outro_text: null,                         // [string]   do you want text AFTER your tweets?
      join_text:  null,                         // [string]   optional text in between date and tweet, try setting to "auto"
      auto_join_text_default: "I said,",        // [string]   auto text for non verb: "I said" bullocks
      auto_join_text_ed: "I",                   // [string]   auto text for past tense: "I" surfed
      auto_join_text_ing: "I am",               // [string]   auto tense for present tense: "I was" surfing
      auto_join_text_reply: "I replied to",     // [string]   auto tense for replies: "I replied to" @someone "with"
      auto_join_text_url: "I was looking at",   // [string]   auto tense for urls: "I was looking at" http:...
      loading_text: null,                       // [string]   optional loading text, displayed while tweets load
      refresh_interval: null,                   // [integer]  optional number of seconds after which to reload tweets
      twitter_url: "twitter.com",               // [string]   custom twitter url, if any (apigee, etc.)
      twitter_api_url: "api.twitter.com",       // [string]   custom twitter api url, if any (apigee, etc.)
      twitter_search_url: "search.twitter.com", // [string]   custom twitter search url, if any (apigee, etc.)
      template: "{avatar}{time}{join}{text}",   // [string or function] template used to construct each tweet
 - see code for available vars
      comparator: function(tweet1, tweet2) {    // [function] comparator used to sort tweets (see Array.sort)
        return tweet2["tweet_time"] - tweet1["tweet_time"];
      },
      filter: function(tweet) {                 // [function] whether or not to include a particular tweet (be sure to also set 'fetch')
        return true;
      }
      // You can attach callbacks to the following events using jQuery's standard .bind() mechanism:
      //   "loaded" -- triggered when tweets have been fetched and rendered
    }, o);

    // See http://daringfireball.net/2010/07/improved_regex_for_matching_urls
    var url_regexp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;

    // Expand values inside simple string templates with {placeholders}
    function t(template, info) {
      if (typeof template === "string") {
        var result = template;
        for(var key in info) {
          var val = info[key];
          result = result.replace(new RegExp('{'+key+'}','g'), val === null ? '' : val);
        }
        return result;
      } else return template(info);
    }
    // Export the t function for use when passing a function as the 'template' option
    $.extend({tweet: {t: t}});

    function replacer (regex, replacement) {
      return function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(regex, replacement));
        });
        return $(returning);
      };
    }

    function escapeHTML(s) {
      return s.replace(/</g,"<").replace(/>/g,"^>");
    }

    $.fn.extend({
      linkUser: replacer(/(^|[\W])@(\w+)/gi, "$1@$2"),
      // Support various latin1 (\u00**) and arabic (\u06**) alphanumeric chars
      linkHash: replacer(/(?:^| )[\#]+([\w\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0600-\u06ff]+)/gi,
                         ' #$1'),
      makeHeart: replacer(/(<)+[3]/gi, "<tt class='heart'>♥</tt>")
    });

    function linkURLs(text, entities) {
      return text.replace(url_regexp, function(match) {
        var url = (/^[a-z]+:/i).test(match) ? match : "http://"+match;
        var text = match;
        for(var i = 0; i < entities.length; ++i) {
          var entity = entities[i];
          if (entity.url == url && entity.expanded_url) {
            url = entity.expanded_url;
            text = entity.display_url;
            break;
          }
        }
        return ""+escapeHTML(text)+"";
      });
    }

    function parse_date(date_str) {
      // The non-search twitter APIs return inconsistently-formatted dates, which Date.parse
      // cannot handle in IE. We therefore perform the following transformation:
      // "Wed Apr 29 08:53:31 +0000 2009" => "Wed, Apr 29 2009 08:53:31 +0000"
      return Date.parse(date_str.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'));
    }

    function extract_relative_time(date) {
      var toInt = function(val) { return parseInt(val, 10); };
      var relative_to = new Date();
      var delta = toInt((relative_to.getTime() - date) / 1000);
      if (delta < 1) delta = 0;
      return {
        days:    toInt(delta / 86400),
        hours:   toInt(delta / 3600),
        minutes: toInt(delta / 60),
        seconds: toInt(delta)
      };
    }

    function format_relative_time(time_ago) {
      if ( time_ago.days > 2 )     return 'about ' + time_ago.days + ' days ago';
      if ( time_ago.hours > 24 )   return 'about a day ago';
      if ( time_ago.hours > 2 )    return 'about ' + time_ago.hours + ' hours ago';
      if ( time_ago.minutes > 45 ) return 'about an hour ago';
      if ( time_ago.minutes > 2 )  return 'about ' + time_ago.minutes + ' minutes ago';
      if ( time_ago.seconds > 1 )  return 'about ' + time_ago.seconds + ' seconds ago';
      return 'just now';
    }

    function build_auto_join_text(text) {
      if (text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
        return s.auto_join_text_reply;
      } else if (text.match(url_regexp)) {
        return s.auto_join_text_url;
      } else if (text.match(/^((\w+ed)|just) .*/im)) {
        return s.auto_join_text_ed;
      } else if (text.match(/^(\w*ing) .*/i)) {
        return s.auto_join_text_ing;
      } else {
        return s.auto_join_text_default;
      }
    }

    function build_api_url() {
      var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
      var count = (s.fetch === null) ? s.count : s.fetch;
      var common_params = '&include_entities=1&callback=?';
      if (s.list) {
        return proto+"//"+s.twitter_api_url+"/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?page="+s.page+"&per_page="+count+common_params;
      } else if (s.favorites) {
        return proto+"//"+s.twitter_api_url+"/favorites/"+s.username[0]+".json?page="+s.page+"&count="+count+common_params;
      } else if (s.query === null && s.username.length == 1) {
        return proto+'//'+s.twitter_api_url+'/1/statuses/user_timeline.json?screen_name='+s.username[0]+'&count='+count+(s.retweets ? '&include_rts=1' : '')+'&page='+s.page+common_params;
      } else {
        var query = (s.query || 'from:'+s.username.join(' OR from:'));
        return proto+'//'+s.twitter_search_url+'/search.json?&q='+encodeURIComponent(query)+'&rpp='+count+'&page='+s.page+common_params;
      }
    }

    function extract_avatar_url(item, secure) {
      if (secure) {
        return ('user' in item) ?
          item.user.profile_image_url_https :
          extract_avatar_url(item, false).
            replace(/^http:\/\/[a-z0-9]{1,3}\.twimg\.com\//, "https://s3.amazonaws.com/twitter_production/");
      } else {
        return item.profile_image_url || item.user.profile_image_url;
      }
    }

    // Convert twitter API objects into data available for
    // constructing each tweet
 using a template
    function extract_template_data(item){
      var o = {};
      o.item = item;
      o.source = item.source;
      o.screen_name = item.from_user || item.user.screen_name;
      // The actual user name is not returned by all Twitter APIs, so please do not
      // file an issue if it is empty:
      o.name = item.from_user_name || item.user.name;
      o.avatar_size = s.avatar_size;
      o.avatar_url = extract_avatar_url(item, (document.location.protocol === 'https:'));
      o.retweet = typeof(item.retweeted_status) != 'undefined';
      o.tweet_time = parse_date(item.created_at);
      o.join_text = s.join_text == "auto" ? build_auto_join_text(item.text) : s.join_text;
      o.tweet_id = item.id_str;
      o.twitter_base = "http://"+s.twitter_url+"/";
      o.user_url = o.twitter_base+o.screen_name;
      o.tweet_url = o.user_url+"/status/"+o.tweet_id;
      o.reply_url = o.twitter_base+"intent/tweet?in_reply_to="+o.tweet_id;
      o.retweet_url = o.twitter_base+"intent/retweet?tweet_id="+o.tweet_id;
      o.favorite_url = o.twitter_base+"intent/favorite?tweet_id="+o.tweet_id;
      o.retweeted_screen_name = o.retweet && item.retweeted_status.user.screen_name;
      o.tweet_relative_time = format_relative_time(extract_relative_time(o.tweet_time));
      o.entities = item.entities ? (item.entities.urls || []).concat(item.entities.media || []) : [];
      o.tweet_raw_text = o.retweet ? ('RT @'+o.retweeted_screen_name+' '+item.retweeted_status.text) : item.text; // avoid '...' in long retweets
      o.tweet_text = $([linkURLs(o.tweet_raw_text, o.entities)]).linkUser().linkHash()[0];
      o.tweet_text_fancy = $([o.tweet_text]).makeHeart()[0];

      // Default spans, and pre-formatted blocks for common layouts
      o.user = t('{screen_name}', o);
      o.join = s.join_text ? t(' {join_text} ', o) : ' ';
      o.avatar = o.avatar_size ?
        t('', o) : '';
      o.time = t('{tweet_relative_time}', o);
      o.text = t('{tweet_text_fancy}', o);
      o.reply_action = t('reply', o);
      o.retweet_action = t('retweet', o);
      o.favorite_action = t('favorite', o);
      return o;
    }

    function load(widget) {
      var intro = '
'+s.intro_text+'
';
      var outro = '
'+s.outro_text+'
';
      var loading = $('
'+s.loading_text+'
');
      if (s.loading_text) $(widget).not(":has(.tweet_list)").empty().append(loading);
      $.getJSON(build_api_url(), function(data){
        var list = $('
');
        var tweets = $.map(data.results || data, extract_template_data);
        tweets = $.grep(tweets, s.filter).sort(s.comparator).slice(0, s.count);
        list.append($.map(tweets, function(o) { return "
" + t(s.template, o) + "
"; }).join('')).
          children('li:first').addClass('tweet_first').end().
          children('li:odd').addClass('tweet_even').end().
          children('li:even').addClass('tweet_odd');

        $(widget).empty().append(list);
        if (s.intro_text) list.before(intro);
        if (s.outro_text) list.after(outro);

        $(widget).trigger("loaded").trigger((tweets.length === 0 ? "empty" : "full"));
        if (s.refresh_interval) {
          window.setTimeout(function() { $(widget).trigger("tweet:load"); }, 1000 * s.refresh_interval);
        }
      });
    }

    return this.each(function(i, widget){
      if(s.username && typeof(s.username) == "string"){
        s.username = [s.username];
      }

      $(widget).unbind("tweet:load").bind("tweet:load", function(){
        load(widget);
      }).trigger("tweet:load");
    });
  };
}));
<commit_msg>Use split/join to more elegantly interpolate values into template<commit_after>// jquery.tweet.js - See http://tweet.seaofclouds.com/ or https://github.com/seaofclouds/tweet for more info
// Copyright (c) 2008-2011 Todd Matthews & Steve Purcell
(function (factory) {
  if (typeof define === 'function' && define.amd)
    define(['jquery'], factory); // AMD support for RequireJS etc.
  else
    factory(jQuery);
}(function ($) {
  $.fn.tweet = function(o){
    var s = $.extend({
      username: null,                           // [string or array] required unless using the 'query' option; one or more twitter screen names (use 'list' option for multiple names, where possible)
      list: null,                               // [string]   optional name of list belonging to username
      favorites: false,                         // [boolean]  display the user's favorites instead of his tweets
      query: null,                              // [string]   optional search query (see also: http://search.twitter.com/operators)
      avatar_size: null,                        // [integer]  height and width of avatar if displayed (48px max)
      count: 3,                                 // [integer]  how many tweets to display?
      fetch: null,                              // [integer]  how many tweets to fetch via the API (set this higher than 'count' if using the 'filter' option)
      page: 1,                                  // [integer]  which page of results to fetch (if count != fetch, you'll get unexpected results)
      retweets: true,                           // [boolean]  whether to fetch (official) retweets (not supported in all display modes)
      intro_text: null,                         // [string]   do you want text BEFORE your your tweets?
      outro_text: null,                         // [string]   do you want text AFTER your tweets?
      join_text:  null,                         // [string]   optional text in between date and tweet, try setting to "auto"
      auto_join_text_default: "I said,",        // [string]   auto text for non verb: "I said" bullocks
      auto_join_text_ed: "I",                   // [string]   auto text for past tense: "I" surfed
      auto_join_text_ing: "I am",               // [string]   auto tense for present tense: "I was" surfing
      auto_join_text_reply: "I replied to",     // [string]   auto tense for replies: "I replied to" @someone "with"
      auto_join_text_url: "I was looking at",   // [string]   auto tense for urls: "I was looking at" http:...
      loading_text: null,                       // [string]   optional loading text, displayed while tweets load
      refresh_interval: null,                   // [integer]  optional number of seconds after which to reload tweets
      twitter_url: "twitter.com",               // [string]   custom twitter url, if any (apigee, etc.)
      twitter_api_url: "api.twitter.com",       // [string]   custom twitter api url, if any (apigee, etc.)
      twitter_search_url: "search.twitter.com", // [string]   custom twitter search url, if any (apigee, etc.)
      template: "{avatar}{time}{join}{text}",   // [string or function] template used to construct each tweet
 - see code for available vars
      comparator: function(tweet1, tweet2) {    // [function] comparator used to sort tweets (see Array.sort)
        return tweet2["tweet_time"] - tweet1["tweet_time"];
      },
      filter: function(tweet) {                 // [function] whether or not to include a particular tweet (be sure to also set 'fetch')
        return true;
      }
      // You can attach callbacks to the following events using jQuery's standard .bind() mechanism:
      //   "loaded" -- triggered when tweets have been fetched and rendered
    }, o);

    // See http://daringfireball.net/2010/07/improved_regex_for_matching_urls
    var url_regexp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;

    // Expand values inside simple string templates with {placeholders}
    function t(template, info) {
      if (typeof template === "string") {
        var result = template;
        for(var key in info) {
          var val = info[key];
          result = result.split('{'+key+'}').join(val === null ? '' : val);
        }
        return result;
      } else return template(info);
    }
    // Export the t function for use when passing a function as the 'template' option
    $.extend({tweet: {t: t}});

    function replacer (regex, replacement) {
      return function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(regex, replacement));
        });
        return $(returning);
      };
    }

    function escapeHTML(s) {
      return s.replace(/</g,"<").replace(/>/g,"^>");
    }

    $.fn.extend({
      linkUser: replacer(/(^|[\W])@(\w+)/gi, "$1@$2"),
      // Support various latin1 (\u00**) and arabic (\u06**) alphanumeric chars
      linkHash: replacer(/(?:^| )[\#]+([\w\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0600-\u06ff]+)/gi,
                         ' #$1'),
      makeHeart: replacer(/(<)+[3]/gi, "<tt class='heart'>♥</tt>")
    });

    function linkURLs(text, entities) {
      return text.replace(url_regexp, function(match) {
        var url = (/^[a-z]+:/i).test(match) ? match : "http://"+match;
        var text = match;
        for(var i = 0; i < entities.length; ++i) {
          var entity = entities[i];
          if (entity.url == url && entity.expanded_url) {
            url = entity.expanded_url;
            text = entity.display_url;
            break;
          }
        }
        return ""+escapeHTML(text)+"";
      });
    }

    function parse_date(date_str) {
      // The non-search twitter APIs return inconsistently-formatted dates, which Date.parse
      // cannot handle in IE. We therefore perform the following transformation:
      // "Wed Apr 29 08:53:31 +0000 2009" => "Wed, Apr 29 2009 08:53:31 +0000"
      return Date.parse(date_str.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'));
    }

    function extract_relative_time(date) {
      var toInt = function(val) { return parseInt(val, 10); };
      var relative_to = new Date();
      var delta = toInt((relative_to.getTime() - date) / 1000);
      if (delta < 1) delta = 0;
      return {
        days:    toInt(delta / 86400),
        hours:   toInt(delta / 3600),
        minutes: toInt(delta / 60),
        seconds: toInt(delta)
      };
    }

    function format_relative_time(time_ago) {
      if ( time_ago.days > 2 )     return 'about ' + time_ago.days + ' days ago';
      if ( time_ago.hours > 24 )   return 'about a day ago';
      if ( time_ago.hours > 2 )    return 'about ' + time_ago.hours + ' hours ago';
      if ( time_ago.minutes > 45 ) return 'about an hour ago';
      if ( time_ago.minutes > 2 )  return 'about ' + time_ago.minutes + ' minutes ago';
      if ( time_ago.seconds > 1 )  return 'about ' + time_ago.seconds + ' seconds ago';
      return 'just now';
    }

    function build_auto_join_text(text) {
      if (text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
        return s.auto_join_text_reply;
      } else if (text.match(url_regexp)) {
        return s.auto_join_text_url;
      } else if (text.match(/^((\w+ed)|just) .*/im)) {
        return s.auto_join_text_ed;
      } else if (text.match(/^(\w*ing) .*/i)) {
        return s.auto_join_text_ing;
      } else {
        return s.auto_join_text_default;
      }
    }

    function build_api_url() {
      var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
      var count = (s.fetch === null) ? s.count : s.fetch;
      var common_params = '&include_entities=1&callback=?';
      if (s.list) {
        return proto+"//"+s.twitter_api_url+"/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?page="+s.page+"&per_page="+count+common_params;
      } else if (s.favorites) {
        return proto+"//"+s.twitter_api_url+"/favorites/"+s.username[0]+".json?page="+s.page+"&count="+count+common_params;
      } else if (s.query === null && s.username.length == 1) {
        return proto+'//'+s.twitter_api_url+'/1/statuses/user_timeline.json?screen_name='+s.username[0]+'&count='+count+(s.retweets ? '&include_rts=1' : '')+'&page='+s.page+common_params;
      } else {
        var query = (s.query || 'from:'+s.username.join(' OR from:'));
        return proto+'//'+s.twitter_search_url+'/search.json?&q='+encodeURIComponent(query)+'&rpp='+count+'&page='+s.page+common_params;
      }
    }

    function extract_avatar_url(item, secure) {
      if (secure) {
        return ('user' in item) ?
          item.user.profile_image_url_https :
          extract_avatar_url(item, false).
            replace(/^http:\/\/[a-z0-9]{1,3}\.twimg\.com\//, "https://s3.amazonaws.com/twitter_production/");
      } else {
        return item.profile_image_url || item.user.profile_image_url;
      }
    }

    // Convert twitter API objects into data available for
    // constructing each tweet
 using a template
    function extract_template_data(item){
      var o = {};
      o.item = item;
      o.source = item.source;
      o.screen_name = item.from_user || item.user.screen_name;
      // The actual user name is not returned by all Twitter APIs, so please do not
      // file an issue if it is empty:
      o.name = item.from_user_name || item.user.name;
      o.avatar_size = s.avatar_size;
      o.avatar_url = extract_avatar_url(item, (document.location.protocol === 'https:'));
      o.retweet = typeof(item.retweeted_status) != 'undefined';
      o.tweet_time = parse_date(item.created_at);
      o.join_text = s.join_text == "auto" ? build_auto_join_text(item.text) : s.join_text;
      o.tweet_id = item.id_str;
      o.twitter_base = "http://"+s.twitter_url+"/";
      o.user_url = o.twitter_base+o.screen_name;
      o.tweet_url = o.user_url+"/status/"+o.tweet_id;
      o.reply_url = o.twitter_base+"intent/tweet?in_reply_to="+o.tweet_id;
      o.retweet_url = o.twitter_base+"intent/retweet?tweet_id="+o.tweet_id;
      o.favorite_url = o.twitter_base+"intent/favorite?tweet_id="+o.tweet_id;
      o.retweeted_screen_name = o.retweet && item.retweeted_status.user.screen_name;
      o.tweet_relative_time = format_relative_time(extract_relative_time(o.tweet_time));
      o.entities = item.entities ? (item.entities.urls || []).concat(item.entities.media || []) : [];
      o.tweet_raw_text = o.retweet ? ('RT @'+o.retweeted_screen_name+' '+item.retweeted_status.text) : item.text; // avoid '...' in long retweets
      o.tweet_text = $([linkURLs(o.tweet_raw_text, o.entities)]).linkUser().linkHash()[0];
      o.tweet_text_fancy = $([o.tweet_text]).makeHeart()[0];

      // Default spans, and pre-formatted blocks for common layouts
      o.user = t('{screen_name}', o);
      o.join = s.join_text ? t(' {join_text} ', o) : ' ';
      o.avatar = o.avatar_size ?
        t('', o) : '';
      o.time = t('{tweet_relative_time}', o);
      o.text = t('{tweet_text_fancy}', o);
      o.reply_action = t('reply', o);
      o.retweet_action = t('retweet', o);
      o.favorite_action = t('favorite', o);
      return o;
    }

    function load(widget) {
      var intro = '
'+s.intro_text+'
';
      var outro = '
'+s.outro_text+'
';
      var loading = $('
'+s.loading_text+'
');
      if (s.loading_text) $(widget).not(":has(.tweet_list)").empty().append(loading);
      $.getJSON(build_api_url(), function(data){
        var list = $('
');
        var tweets = $.map(data.results || data, extract_template_data);
        tweets = $.grep(tweets, s.filter).sort(s.comparator).slice(0, s.count);
        list.append($.map(tweets, function(o) { return "
" + t(s.template, o) + "
"; }).join('')).
          children('li:first').addClass('tweet_first').end().
          children('li:odd').addClass('tweet_even').end().
          children('li:even').addClass('tweet_odd');

        $(widget).empty().append(list);
        if (s.intro_text) list.before(intro);
        if (s.outro_text) list.after(outro);

        $(widget).trigger("loaded").trigger((tweets.length === 0 ? "empty" : "full"));
        if (s.refresh_interval) {
          window.setTimeout(function() { $(widget).trigger("tweet:load"); }, 1000 * s.refresh_interval);
        }
      });
    }

    return this.each(function(i, widget){
      if(s.username && typeof(s.username) == "string"){
        s.username = [s.username];
      }

      $(widget).unbind("tweet:load").bind("tweet:load", function(){
        load(widget);
      }).trigger("tweet:load");
    });
  };
}));
<|endoftext|>


<commit_before>(function() {
  "use strict"

var Theory = Gibber.Theory = {
  Teoria: window.teoria,
  Scale : function(_root, _mode) {
  	var that = {
  		root: typeof _root === "string" ? teoria.note(_root) : _root,
  		notes: [],

  		chord : function(_notes, _offset) {
  			var _chord = [];
  			_offset = _offset || 0;

  			for(var i = 0; i < _notes.length; i++) {
  				_chord.push( this.notes[ _notes[i] + _offset ] );
  			}
  			return _chord;
  		},

  		create : function() {
  			this.notes = [];
  			if( Gibber.Theory.Scales[ this.mode ] ) {
  				var scale = Gibber.Theory.Scales[ this.mode ]( this.root );
  				scale.create();
  				this.notes = scale.notes;
  			}else{
  				var _rootoctave = this.root.octave;

  				var _scale = teoria.scale.list( this.root, this.mode, false );
  				for(var oct = _rootoctave, o = 0; oct < _rootoctave + 8; oct++, o++) {
  					for(var num = 0; num < _scale.length; num++) {
  						var nt = $.extend( {}, _scale[ num ] );
  						nt.octave += o;
  						this.notes.push( nt );
  					}
  				}

  				var negCount = -1;
  				for(var oct = _rootoctave - 2, o = -1; oct >= 0; oct--, o--) {
  				 	for(var num = _scale.length - 1; num >= 0; num--) {
  				 		var nt = $.extend( {}, _scale[ num ] );
  				 		nt.octave += o;
  				 		this.notes[ negCount-- ] = nt;
  				 	}
  				}
  			}
  		},

  		set : function(_root, _mode) {
  			if(Array.isArray(arguments[0])) {
  				this.root = arguments[0][0];
  				this.mode = arguments[0][1];
  			}else{
  				this.root = _root;
  				this.mode = _mode;
  			}
  		},
  	};

  	var mode = _mode || "aeolian";
  	Object.defineProperty( that, "mode", {
  		get: function() { return mode; },
  		set: function( val ) { mode = val; this.create(); }
  	});

  	var root = that.root;
  	Object.defineProperty(that, "root", {
  		get: function() { return root; },
  		set: function(val) {
  			root = typeof val === "string" ? teoria.note( val ) : val;
  			this.create();
  		}
  	});

  	that.create();

  	return that;
  },

  CustomScale : function() {
    var that = {
      notes : [],
      ratios: arguments[1] || [ 1, 1.10, 1.25, 1.3333, 1.5, 1.666, 1.75 ],

      create : function() {
        this.notes = [];

        var scaleRoot = this.root;

        for( var octave = 0; octave < 8; octave++ ) {
          for( var num = 0; num < this.ratios.length; num++ ) {
            this.notes.push( scaleRoot * this.ratios[num] );
          }
          scaleRoot *= 2;
        }

        scaleRoot = this.root;
  	    var negCount = 8;
        for(var octave = -1; octave >= -8; octave--) {
          scaleRoot /= 2;
          for( var num = 0; num < this.ratios.length; num++ ) {
  		      var noteNum = octave * this.ratios.length + num;
            this.notes[noteNum] = scaleRoot * this.ratios[num];
          }
        }
      },

    	chord : function(_notes, _offset) {
    		var _chord = [];
    		_offset = _offset || 0;

    		for(var i = 0; i < _notes.length; i++) {
    			_chord.push( this.notes[_notes[i] + _offset] );
    		}
    		return _chord;
    	},
    }

    var _root = arguments[0] || 440;
    Object.defineProperty(that, "root", {
      get : function() { return _root; },

      set : function(val) {
    		if(typeof val === "number") {
    			_root = val;
    		}else if (typeof val === "string") {
    			_root = Theory.Teoria.note(val).fq();
    		}else if (typeof val === 'object') {
    			_root = val.fq();
    		}

    		this.create(_root);
      }
    });

  	// var mode = _mode || "aeolian";
//     Object.defineProperty( that, "mode", {
//       get: function() { return mode; },
//       set: function( val ) { mode = val; this.create(); }
//     });

    that.root = _root;
    that.create();

    return that;
  },

  Scales : {
  	// Scales contributed by Luke Taylor
  	// Half-Whole or Octatonic Scale
  	//http://en.wikipedia.org/wiki/Octatonic_scale
  	HalfWhole : function(root) { return Theory.CustomScale( root, [ 1,1.059463,1.189207,1.259921,1.414214,1.498307,1.681793, 1.781797 ]); },

  	//Whole-Half or Octatonic Scale http://en.wikipedia.org/wiki/Octatonic_scale
  	WholeHalf : function(root) { return Theory.CustomScale( root, [ 1,1.122462,1.189207,1.334840,1.414214,1.587401,1.681793, 1.887749 ]); },

  	//Pythagorean Tuning http://en.wikipedia.org/wiki/Pythagorean_tuning

  	//Chromatic scale in Pythagorean tuning
  	Pythagorean : function(root) { return Theory.CustomScale( root, [ 1, 256/243, 9/8, 32/27, 81/64, 4/3, 729/512, 3/2, 128/81, 27/16, 16/9, 243/128 ]); },

  	//Major Pythagorean
  	PythagoreanMajor : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 81/64, 4/3, 3/2, 27/16, 243/128 ]); },

  	//Major Pythagorean
  	PythagoreanMinor : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 32/27, 4/3, 3/2, 128/81, 16/9 ]); },

  	// 5-limit Just Intonation http://en.wikipedia.org/wiki/List_of_intervals_in_5-limit_just_intonation
  	//Chromatic scale in 5-limit just intonation
  	Limit5 : function(root) { return Theory.CustomScale( root, [ 1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 9/5, 15/8 ]); },

  	//Major scale in 5-limit
  	Limit5Major : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8 ]); },

  	//Minor scale in 5-limit
  	Limit5Minor : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 6/5, 4/3, 3/2, 8/5, 9/5 ]); },

  	// Messiaen's modes of limited transposition http://en.wikipedia.org/wiki/Modes_of_limited_transposition
  	Mess3 : function(root) { return Theory.CustomScale( root, [1,1.122462, 1.189207, 1.259921, 1.414214, 1.498307, 1.587401, 1.781797, 1.887749 ]) },
  	Mess4 : function(root) { return Theory.CustomScale( root, [1, 1.059463, 1.122462, 1.334840, 1.414214, 1.498307, 1.587401, 1.887749 ]) },
  	Mess5 : function(root) { return Theory.CustomScale( root, [1, 1.059463, 1.334840, 1.414214, 1.498307, 1.887749 ]) },
  	Mess6 : function(root) { return Theory.CustomScale( root, [1, 1.122462, 1.259921, 1.334840, 1.414214, 1.587401, 1.781797, 1.887749 ]) },
  	Mess7 : function(root) { return Theory.CustomScale( root, [1, 1.059463, 1.122462, 1.189207, 1.334840, 1.414214, 1.498307, 1.587401, 1.681793, 1.887749 ]) },

  	//And, a personal (anthony garcia) favorite synthetic mode, lydian flat 7:
  	Adams : function(root) { return Theory.CustomScale( root, [1, 1.122462, 1.259921, 1.414214, 1.498307, 1.681793, 1.781797 ]) },

  	//5 tone equal temperament //http://en.wikipedia.org/wiki/Equal_temperament#5_and_7_tone_temperaments_in_ethnomusicology
  	Equal5Tone : function(root) { return Theory.CustomScale( root, [ 1, 1.15, 1.32, 1.35, 1.52, 1.74 ]); },

  	//7 tone equal temperament
  	//http://en.wikipedia.org/wiki/Equal_temperament#5_and_7_tone_temperaments_in_ethnomusicology
  	Equal7Tone : function(root) { return Theory.CustomScale( root, [ 1, 1.1, 1.22, 1.35, 1.49, 1.64, 1.81 ]); },

  	Just : function(root) { return Theory.CustomScale( root, [ 1, 1.0417, 1.1250, 1.2000, 1.2500, 1.3333, 1.4063, 1.5, 1.6, 1.6667, 1.8, 1.8750] ); },
  },

	chord : function( val, volume ) {
		this.notation = val;

		if( typeof this.notation === "string" ) {
			var _root = this.notation.slice( 0,1 ),
          _octave,
          _quality;

			if( isNaN( this.notation.charAt( 1 ) ) ) { 	// if true, then there is a sharp or flat...
				_root += this.notation.charAt( 1 );	// ... so add it to the root name
				_octave = parseInt( this.notation.slice( 2,3 ) );
				_quality = this.notation.slice( 3 );
			}else{
				_octave = parseInt( this.notation.slice( 1, 2 ) );
				_quality = this.notation.slice( 2 );
			}

			var _chord = teoria.note( _root + _octave ).chord( _quality );
			for( var j = 0; j < _chord.notes.length; j++ ) {
				var n = _chord.notes[ j ];
				this.note( typeof note === 'number' ? note : n.fq() );
			}
		}else{
			for( var k = 0; k < this.notation.length; k++ ) {
				var note = this.notation[ k ];
				this.note( typeof note === 'number' || typeof note === 'string' ? note : note.fq() );
			}
		}

		if( typeof arguments[ 1 ] !== "undefined" ) {
			this.amp = arguments[ 1 ];
		}

		return this;
	},

}

window.Scale = Gibber.Theory.Scale

})()<commit_msg>added Shruti scale<commit_after>(function() {
  "use strict"

var Theory = Gibber.Theory = {
  Teoria: window.teoria,
  Scale : function(_root, _mode) {
  	var that = {
  		root: typeof _root === "string" ? teoria.note(_root) : _root,
  		notes: [],

  		chord : function(_notes, _offset) {
  			var _chord = [];
  			_offset = _offset || 0;

  			for(var i = 0; i < _notes.length; i++) {
  				_chord.push( this.notes[ _notes[i] + _offset ] );
  			}
  			return _chord;
  		},

  		create : function() {
  			this.notes = [];
  			if( Gibber.Theory.Scales[ this.mode ] ) {
  				var scale = Gibber.Theory.Scales[ this.mode ]( this.root );
  				scale.create();
  				this.notes = scale.notes;
  			}else{
  				var _rootoctave = this.root.octave;

  				var _scale = teoria.scale.list( this.root, this.mode, false );
  				for(var oct = _rootoctave, o = 0; oct < _rootoctave + 8; oct++, o++) {
  					for(var num = 0; num < _scale.length; num++) {
  						var nt = $.extend( {}, _scale[ num ] );
  						nt.octave += o;
  						this.notes.push( nt );
  					}
  				}

  				var negCount = -1;
  				for(var oct = _rootoctave - 2, o = -1; oct >= 0; oct--, o--) {
  				 	for(var num = _scale.length - 1; num >= 0; num--) {
  				 		var nt = $.extend( {}, _scale[ num ] );
  				 		nt.octave += o;
  				 		this.notes[ negCount-- ] = nt;
  				 	}
  				}
  			}
  		},

  		set : function(_root, _mode) {
  			if(Array.isArray(arguments[0])) {
  				this.root = arguments[0][0];
  				this.mode = arguments[0][1];
  			}else{
  				this.root = _root;
  				this.mode = _mode;
  			}
  		},
  	};

  	var mode = _mode || "aeolian";
  	Object.defineProperty( that, "mode", {
  		get: function() { return mode; },
  		set: function( val ) { mode = val; this.create(); }
  	});

  	var root = that.root;
  	Object.defineProperty(that, "root", {
  		get: function() { return root; },
  		set: function(val) {
  			root = typeof val === "string" ? teoria.note( val ) : val;
  			this.create();
  		}
  	});

  	that.create();

  	return that;
  },

  CustomScale : function() {
    var that = {
      notes : [],
      ratios: arguments[1] || [ 1, 1.10, 1.25, 1.3333, 1.5, 1.666, 1.75 ],

      create : function() {
        this.notes = [];

        var scaleRoot = this.root;

        for( var octave = 0; octave < 8; octave++ ) {
          for( var num = 0; num < this.ratios.length; num++ ) {
            this.notes.push( scaleRoot * this.ratios[num] );
          }
          scaleRoot *= 2;
        }

        scaleRoot = this.root;
  	    var negCount = 8;
        for(var octave = -1; octave >= -8; octave--) {
          scaleRoot /= 2;
          for( var num = 0; num < this.ratios.length; num++ ) {
  		      var noteNum = octave * this.ratios.length + num;
            this.notes[noteNum] = scaleRoot * this.ratios[num];
          }
        }
      },

    	chord : function(_notes, _offset) {
    		var _chord = [];
    		_offset = _offset || 0;

    		for(var i = 0; i < _notes.length; i++) {
    			_chord.push( this.notes[_notes[i] + _offset] );
    		}
    		return _chord;
    	},
    }

    var _root = arguments[0] || 440;
    Object.defineProperty(that, "root", {
      get : function() { return _root; },

      set : function(val) {
    		if(typeof val === "number") {
    			_root = val;
    		}else if (typeof val === "string") {
    			_root = Theory.Teoria.note(val).fq();
    		}else if (typeof val === 'object') {
    			_root = val.fq();
    		}

    		this.create(_root);
      }
    });

  	// var mode = _mode || "aeolian";
//     Object.defineProperty( that, "mode", {
//       get: function() { return mode; },
//       set: function( val ) { mode = val; this.create(); }
//     });

    that.root = _root;
    that.create();

    return that;
  },

  Scales : {
  	// Scales contributed by Luke Taylor
  	// Half-Whole or Octatonic Scale
  	//http://en.wikipedia.org/wiki/Octatonic_scale
  	HalfWhole : function(root) { return Theory.CustomScale( root, [ 1,1.059463,1.189207,1.259921,1.414214,1.498307,1.681793, 1.781797 ]); },

  	//Whole-Half or Octatonic Scale http://en.wikipedia.org/wiki/Octatonic_scale
  	WholeHalf : function(root) { return Theory.CustomScale( root, [ 1,1.122462,1.189207,1.334840,1.414214,1.587401,1.681793, 1.887749 ]); },

  	//Pythagorean Tuning http://en.wikipedia.org/wiki/Pythagorean_tuning

  	//Chromatic scale in Pythagorean tuning
  	Pythagorean : function(root) { return Theory.CustomScale( root, [ 1, 256/243, 9/8, 32/27, 81/64, 4/3, 729/512, 3/2, 128/81, 27/16, 16/9, 243/128 ]); },

  	//Major Pythagorean
  	PythagoreanMajor : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 81/64, 4/3, 3/2, 27/16, 243/128 ]); },

  	//Major Pythagorean
  	PythagoreanMinor : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 32/27, 4/3, 3/2, 128/81, 16/9 ]); },

  	// 5-limit Just Intonation http://en.wikipedia.org/wiki/List_of_intervals_in_5-limit_just_intonation
  	//Chromatic scale in 5-limit just intonation
  	Limit5 : function(root) { return Theory.CustomScale( root, [ 1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 9/5, 15/8 ]); },

  	//Major scale in 5-limit
  	Limit5Major : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8 ]); },

  	//Minor scale in 5-limit
  	Limit5Minor : function(root) { return Theory.CustomScale( root, [ 1, 9/8, 6/5, 4/3, 3/2, 8/5, 9/5 ]); },

  	// Messiaen's modes of limited transposition http://en.wikipedia.org/wiki/Modes_of_limited_transposition
  	Mess3 : function(root) { return Theory.CustomScale( root, [1,1.122462, 1.189207, 1.259921, 1.414214, 1.498307, 1.587401, 1.781797, 1.887749 ]) },
  	Mess4 : function(root) { return Theory.CustomScale( root, [1, 1.059463, 1.122462, 1.334840, 1.414214, 1.498307, 1.587401, 1.887749 ]) },
  	Mess5 : function(root) { return Theory.CustomScale( root, [1, 1.059463, 1.334840, 1.414214, 1.498307, 1.887749 ]) },
  	Mess6 : function(root) { return Theory.CustomScale( root, [1, 1.122462, 1.259921, 1.334840, 1.414214, 1.587401, 1.781797, 1.887749 ]) },
  	Mess7 : function(root) { return Theory.CustomScale( root, [1, 1.059463, 1.122462, 1.189207, 1.334840, 1.414214, 1.498307, 1.587401, 1.681793, 1.887749 ]) },

  	//And, a personal (anthony garcia) favorite synthetic mode, lydian flat 7:
  	Adams : function(root) { return Theory.CustomScale( root, [1, 1.122462, 1.259921, 1.414214, 1.498307, 1.681793, 1.781797 ]) },

  	//5 tone equal temperament //http://en.wikipedia.org/wiki/Equal_temperament#5_and_7_tone_temperaments_in_ethnomusicology
  	Equal5Tone : function(root) { return Theory.CustomScale( root, [ 1, 1.15, 1.32, 1.35, 1.52, 1.74 ]); },

  	//7 tone equal temperament
  	//http://en.wikipedia.org/wiki/Equal_temperament#5_and_7_tone_temperaments_in_ethnomusicology
  	Equal7Tone : function(root) { return Theory.CustomScale( root, [ 1, 1.1, 1.22, 1.35, 1.49, 1.64, 1.81 ]); },

  	Just : function(root) { return Theory.CustomScale( root, [ 1, 1.0417, 1.1250, 1.2000, 1.2500, 1.3333, 1.4063, 1.5, 1.6, 1.6667, 1.8, 1.8750] ); },

    Shruti: function(root) { return Theory.CustomScale( root, [1,256/243,16/15,10/9,9/8,32/27,6/5,5/4,81/64,4/3,27/20,45/32,729/512,3/2,128/81,8/5,5/3,27/16,16/9,9/5,15/8,243/128,2] ); },
  },

	chord : function( val, volume ) {
		this.notation = val;

		if( typeof this.notation === "string" ) {
			var _root = this.notation.slice( 0,1 ),
          _octave,
          _quality;

			if( isNaN( this.notation.charAt( 1 ) ) ) { 	// if true, then there is a sharp or flat...
				_root += this.notation.charAt( 1 );	// ... so add it to the root name
				_octave = parseInt( this.notation.slice( 2,3 ) );
				_quality = this.notation.slice( 3 );
			}else{
				_octave = parseInt( this.notation.slice( 1, 2 ) );
				_quality = this.notation.slice( 2 );
			}

			var _chord = teoria.note( _root + _octave ).chord( _quality );
			for( var j = 0; j < _chord.notes.length; j++ ) {
				var n = _chord.notes[ j ];
				this.note( typeof note === 'number' ? note : n.fq() );
			}
		}else{
			for( var k = 0; k < this.notation.length; k++ ) {
				var note = this.notation[ k ];
				this.note( typeof note === 'number' || typeof note === 'string' ? note : note.fq() );
			}
		}

		if( typeof arguments[ 1 ] !== "undefined" ) {
			this.amp = arguments[ 1 ];
		}

		return this;
	},

}

window.Scale = Gibber.Theory.Scale

})()<|endoftext|>