File: jzik.js - Tab length: 1 2 4 8 - Lines: on off - No wrap: on off

    /*
        jZik javascript sampler, by r043v/dph/m2m -- noferov@gmail.com
        under creative commons 3.0 by-nc-sa license -- http://creativecommons.org/licenses/by-nc-sa/3.0/
       
        this plugin is dedicated to read audio sprites using html5 audio api
        it take only one source sound file, copy it to create some channels and play inside small part of the long source file
        support delay play and repeat, volume or play rate option, and callback on play, stop and repeat event
    */


    function jzik(sources,samples,channels,onInit)
    {   var zik = new Object({audio:document.createElement('audio'),source:""});
        if(!zik.audio.canPlayType) return false;
        if(sources.ogg !== undefined) if(!!(zik.audio.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''))) zik.source="ogg";
        if(zik.source == "") if(sources.mp3 !== undefined) if(!!(zik.audio.canPlayType('audio/mpeg;').replace(/no/, ''))) zik.source="mp3";
        if(zik.source == "") if(sources.wav !== undefined) if(!!(zik.audio.canPlayType('audio/wav; codecs="1"').replace(/no/, ''))) zik.source="wav";
        if(zik.source == "") if(sources.aac !== undefined) if(!!(zik.audio.canPlayType('audio/mp4; codecs="mp4a.40.2"').replace(/no/, ''))) zik.source="aac";
        if(zik.source == "") return false;

        for(n in samples){ var spl = samples[n]; if(spl.end === undefined){ if(spl.size === undefined) return false; spl.end = spl.start + spl.size; } };

        zik.channelsNb = channels;
        zik.samples = samples;
        zik.channels = [];
        zik.activePlay = [];
        zik.checkTimer = false;
       
        zik.audio.src = sources[zik.source]; zik.audio.autobuffer=true; zik.audio.preload='auto'; zik.audio.loop=false; document.body.appendChild(zik.audio);
        zik.callInit = function(){ if(jzikIsFunction(onInit)) onInit.call(zik); };
        zik.channelsLoaded = 1;
       
        zik.cloneSampleLoaded = function()
        {   if(this.jzloaded || this.readyState < 4) return; this.jzloaded = true; //this.removeEventListener('canplaythrough',zik.cloneSampleLoaded,false);
            if(++zik.channelsLoaded >= zik.channelsNb) zik.callInit();
        };
       
        zik.masterSampleLoaded = function()
        {   if(this.jzloaded || this.readyState < 4) return; this.jzloaded = true; //this.removeEventListener('canplaythrough',zik.masterSampleLoaded,false);
            zik.channels[0] = new Object({isplay:false,isfree:true,audio:zik.audio});
            if(zik.channelsNb == 1) zik.callInit();
              else
            for(var n=1; n < zik.channelsNb; n++)
            {   zik.channels[n] = new Object({isplay:false,isfree:true,audio:zik.audio.cloneNode(true)});
                var a = zik.channels[n].audio; a.jzloaded = false;
                a.addEventListener('canplaythrough',zik.cloneSampleLoaded,false);
                if(window.opera !== undefined) zik.channels[n].audio.load(); // manual load for call event in opera.
            }
        };
       
        zik.audio.jzloaded = false;
        zik.audio.addEventListener('canplaythrough',zik.masterSampleLoaded,false);
        zik.play = jzikplay; zik.check = function(){ jzikcheck.call(zik); };
        zik.audio.load(); return zik;
    };

    function jzikcheck()
    {   var zik = this;
        for(n in zik.activePlay)
        {   var chn = zik.channels[zik.activePlay[n]]; var opt = chn.opt;
            if(chn.isfree || !chn.isplay) continue;
            if(chn.startTime != 0)
            {   var ctime = new Date(); ctime = ctime.getTime();
                if(chn.startTime <= ctime) { chn.startTime=0; chn.audio.play(); if(jzikIsFunction(opt.onPlay)) opt.onPlay.call(chn); }
            } else {
                if(chn.audio.currentTime >= chn.stopOffset)
                {   chn.audio.pause();
                    if(opt.repeat > 0) opt.repeat--;
                    if(opt.repeat != 0)
                    {   chn.audio.currentTime = chn.startOffset; chn.audio.play();
                        if(jzikIsFunction(opt.onRepeat)) opt.onRepeat.call(chn);
                    } else {
                        if(jzikIsFunction(opt.onEnd)) opt.onEnd.call(chn);
                        chn.isplay = false; zik.activePlay.splice(n,1); chn.isfree = true;
                    }
                }
            }
        }
        if(zik.activePlay.length <= 0) { clearInterval(zik.checkTimer); zik.checkTimer = false; }
    };
   
    function jzikplay(sample, opts)
    {   var zik = this;
        var opt = { repeat:0,speed:1,volume:1,delay:0,updateTime:20,onPlay:false,onEnd:false,onRepeat:false }; for(i in opts) opt[i] = opts[i];
        var spl = zik.samples[sample]; if(spl === undefined) return false;
        var nchn = 0; while(nchn < zik.channelsNb && (false === zik.channels[nchn].isfree)) nchn++; if(nchn >= zik.channelsNb) return false;
        var chn = zik.channels[nchn];
        chn.isfree = false;
        chn.opt = opt;
        chn.audio.volume = opt.volume;
        chn.audio.loop = false;
        chn.audio.playbackRate = opt.speed;
        chn.startOffset = spl.start; chn.stopOffset = spl.end; if(chn.stopOffset > chn.audio.duration) chn.stopOffset = chn.audio.duration;
        chn.audio.currentTime = spl.start;
        if(opt.delay)
        {   chn.audio.pause(); // firefox autoplay fix
            var ctime = new Date(); ctime = ctime.getTime();
            chn.startTime = ctime+chn.delay;
        } else { chn.startTime = 0; chn.audio.play(); if(jzikIsFunction(opt.onPlay)) opt.onPlay.call(chn); }
        chn.isplay = true; zik.activePlay.push(nchn);
        if(zik.checkTimer === false) zik.checkTimer = setInterval(zik.check,opt.updateTime);
        return true;
    };
   
    function jzikIsFunction(f){ var getType = {}; return f && getType.toString.call(f) == '[object Function]'; }