App-MHFS

 view release on metacpan or  search on metacpan

share/public_html/static/music_worklet_inprogress/decoder/mhfscl.js  view on Meta::CPAN

    };
    
    signal.addEventListener('abort', handler);            
    xhr.open(method, url);
    xhr.responseType = 'arraybuffer';
    xhr.setRequestHeader('Range', 'bytes='+start+'-'+end);
    xhr.onload = function () {
        signal.removeEventListener('abort', handler);
        if (this.status >= 200 && this.status < 300) {
            //console.log('xhr success');
            resolve(xhr);
        } else {
            console.log('xhr fail');                   
            reject({
                status: this.status,
                statusText: xhr.statusText
            });
        }
    };
    xhr.onerror = function () {
        console.log('xhr onerror');
        signal.removeEventListener('abort', handler);
        reject({
            status: this.status,
            statusText: xhr.statusText
        });
    };
    
    xhr.onabort = function() {
        console.log('xhr onabort');
        signal.removeEventListener('abort', handler);
        reject({
            status: this.status,
            statusText: xhr.statusText
        });
    };
    xhr.send();
    //console.log('sending xhr');
});
}

const DownloadManager = function(chunksize) {
    const that = {};
    that.CHUNKSIZE = chunksize;

    that._newDownload = async function(url, startOffset) {
        that.done = 0;
        that.url = url;
        that.aController = new AbortController();
        that.acSignal = that.aController.signal;
        that.curOffset = startOffset;
        that.fetchResponse = await fetch(url, {
            signal: that.acSignal,
            headers: {
                'Range': 'bytes='+startOffset+'-'
            }
        });
        const contentrange = that.fetchResponse.headers.get('Content-Range');
        const re = new RegExp('/([0-9]+)');
        const res = re.exec(contentrange);
        if(!res) throw("Failed to get filesize");
        that.size = Number(res[1]);
        that.reader = that.fetchResponse.body.getReader();
        that.data = new Uint8Array(0);
        that.headers = {};
        const ct = that.fetchResponse.headers.get('Content-Type');
        if(ct) {
            that.headers['Content-Type'] = ct;
        }
        const tpcmcnt = that.fetchResponse.headers.get('X-MHFS-totalPCMFrameCount');
        if(tpcmcnt) {
            that.headers['X-MHFS-totalPCMFrameCount'] = tpcmcnt;
        }
    };

    that._AbortIfExists = function() {
        if(that.aController) {
            console.log('abort req');
            that.aController.abort();
            that.aController = null;
        }
    };

    that.GetChunk = async function(url, startOffset, signal) {
        if(that.inuse) {
            throw("GetChunk is inuse");
        }
        that.inuse = 1;

        try {
            if(that.ExternalSignal) {
                that.ExternalSignal.removeEventListener('abort', that._AbortIfExists);
            }
            that.ExternalSignal = signal;
            that.ExternalSignal.addEventListener('abort', that._AbortIfExists);

            const sd = (that.curOffset === startOffset) ? ' SAME' : ' DIFF DFDFDFDFDFFSDFSFS';
            console.log('curOffset '+ that.curOffset + 'startOffset ' + startOffset + sd);

            // if the url doesn't match or the offset isn't within range, launch a new request
            //if((url !== that.url) || (that.curOffset !== startOffset)) {
            if((url !== that.url) || (startOffset < that.curOffset) || ((that.curOffset + that.data.byteLength) < startOffset)) {
                console.log('abort from url or size');
                that._AbortIfExists();
                await that._newDownload(url, startOffset);
            }
            // skip to the requested data
            else if(that.curOffset !== startOffset) {
                const toskip = startOffset - that.curOffset;
                that.data = new Uint8Array(that.data.subarray(toskip));
                that.curOffset = startOffset;
            }
            for(;;) {
                if((that.data.byteLength >= that.CHUNKSIZE) || that.done) {
                    const maxread = Math.min(that.data.byteLength, that.CHUNKSIZE);
                    const tmp = new Uint8Array(that.data.subarray(0, maxread));
                    that.data = new Uint8Array(that.data.subarray(maxread));
                    that.curOffset += maxread;
                    //console.log('set CI to ' + that.curOffset + ' tmp length ' + tmp.byteLength);
                    return {'filesize' : that.size, 'data' : tmp, 'headers' : that.headers};
                }
                const { value: chunk, done: readerDone } = await that.reader.read();
                if(chunk) {
                    const tmp = new Uint8Array(that.data.byteLength + chunk.byteLength);
                    tmp.set(that.data, 0);
                    tmp.set(chunk, that.data.byteLength);
                    that.data = tmp;
                }
                that.done = readerDone;
            }
        }
        catch(err) {
            if(err.name === "AbortError") {
                throw('AbortError');
            }
            else {
                throw('other that.GetChunk error');
            }
        }
        finally {
            that.inuse = 0;
        }
    };


    return that;

};

const GetFileSize = function(xhr) {
    let re = new RegExp('/([0-9]+)');
    let res = re.exec(xhr.getResponseHeader('Content-Range'));
    if(!res) throw("Failed to get filesize");
    return Number(res[1]);
};

const DefDownloadManager = function(chunksize) {
    const that = {};
    that.CHUNKSIZE = chunksize;
    that.curOffset;
    that.GetChunk = async function(url, startOffset, signal) {
        const sd = (that.curOffset === startOffset) ? ' SAME' : ' DIFF DFDFDFDFDFFSDFSFS';
        //console.log('curOffset '+ that.curOffset + 'startOffset ' + startOffset + sd);
        const def_end = startOffset+that.CHUNKSIZE-1;
        const end = that.filesize ? Math.min(def_end, that.filesize-1) : def_end;
        const xhr = await makeRequest('GET', url, startOffset, end, signal);
        that.filesize = GetFileSize(xhr);
        const headers = {};
        const ct = xhr.getResponseHeader('Content-Type');
        if(ct) {
            headers['Content-Type'] = ct;
        }
        const tpcmcnt = xhr.getResponseHeader('X-MHFS-totalPCMFrameCount');
        if(tpcmcnt) {
            headers['X-MHFS-totalPCMFrameCount'] = tpcmcnt;
        }
        that.curOffset = startOffset + xhr.response.byteLength;

        return {'filesize' : that.filesize, 'data' : new Uint8Array(xhr.response), 'headers' : headers};
    };

    return that;
};

const ObjectMap = function() {
    const that = {};
    that.data = [];
    that.addData = function(data) {
        for( let i = 0; i < that.data.length; i++) {
            if(that.data[i] === null) {
                that.data[i] = data;
                return i;
            }
        }
        const di = that.data.length;
        that.data.push(data);
        return di;
    };
    that.removeData = function(di) {
        that.data[di] = null;
        while((di+1) === that.data.length) {
            that.data.length--;
            di--;
            if(that.data.length === 0) break;
        }
    };
    that.getData = function(di) {
        return that.data[di];
    };
    return that;
};

const MHFSCLObjectMap = ObjectMap();

share/public_html/static/music_worklet_inprogress/decoder/mhfscl.js  view on Meta::CPAN

    }
    else if(blockType == MHFSCL.MHFS_CL_TRACK_M_TAGS) {
        mhfscltrack.tags = {};
        do {
            const pComment = MHFSCL.Module._mhfs_cl_track_meta_tags_next_comment(pBlock);
            if(pComment === 0) break;
            const comment = MHFSCL.mhfs_cl_track_meta_tags_comment.from(pComment);
            const strcomment = MHFSCL.Module.UTF8ToString(comment.get('comment'), comment.get('commentSize'));
            console.log('comment: ' + strcomment);
            const [key, value] = strcomment.split('=', 2);
            mhfscltrack.tags[key.toUpperCase()] = value;
        } while(1);
        if(mhfscltrack.tags['TITLE'] && mhfscltrack.tags['ARTIST'] && mhfscltrack.tags['ALBUM'] ) {
            mhfscltrack.mediametadata = {
                'title' : mhfscltrack.tags['TITLE'],
                'artist' : mhfscltrack.tags['ARTIST'],
                'album' : mhfscltrack.tags['ALBUM']
            };
        }
    }
    else if(blockType === MHFSCL.MHFS_CL_TRACK_M_PICTURE) {
        const picture = MHFSCL.mhfs_cl_track_meta_picture.from(pBlock);
        const pictureType = picture.get('pictureType');
        console.log('pictureType ' + pictureType);
        let setPicture = !mhfscltrack.picture;
        if(!setPicture) {
            // pcover top priority, disc second priority, everything else same priority
            switch(pictureType)
            {
                case 6:
                if(mhfscltrack.picture.type === 6) break;
                case 3:
                setPicture = (mhfscltrack.picture.type !== 3);
            }
        }
        if(setPicture) {
            mhfscltrack.picture = {
                type : pictureType,
                mime : MHFSCL.Module.UTF8ToString(picture.get('mime'), picture.get('mimeSize')),
                pictureSize : picture.get('pictureDataSize'),
                pPicture : picture.get('pictureData')
            };
        }
    }
};

const MHFSCLTrack = async function(gsignal, theURL, DLMGR) {
    if(!MHFSCL.ready) {
        console.log('MHFSCLTrack, waiting for MHFSCL to be ready');
        await waitForEvent(MHFSCL, 'ready');
    }
    const that = {};
    that.CHUNKSIZE = 262144;
    that.url = theURL;

    DLMGR ||= DefDownloadManager(that.CHUNKSIZE);

    that._downloadChunk = async function(start, mysignal) {
        if(start % that.CHUNKSIZE)
        {
            throw("start is not a multiple of CHUNKSIZE: " + start);
        }
        const chunk = await DLMGR.GetChunk(theURL, start, mysignal);
        that.filesize = chunk.filesize;
        return chunk;
    };

    that._storeChunk = function(chunk, start) {
        let blockptr = MHFSCL.Module._mhfs_cl_track_add_block(that.ptr, start, that.filesize);
        if(!blockptr)
        {
            throw("failed MHFSCL.Module._mhfs_cl_track_add_block");
        }
        let dataHeap = new Uint8Array(MHFSCL.Module.HEAPU8.buffer, blockptr, chunk.data.byteLength);
        dataHeap.set(chunk.data);
    };

    that.downloadAndStoreChunk = async function(start, mysignal) {
        const chunk = await that._downloadChunk(start, mysignal);
        that._storeChunk(chunk, start);
        return chunk;
    };

    that.close = function() {
        if(that.ptr){
            if(that.initialized) {
                MHFSCL.Module._mhfs_cl_track_deinit(that.ptr);
            }
            MHFSCL.Module._free(that.ptr);
            that.ptr = null;
        }                    
    };
    
    that.seek = function(pcmFrameIndex) {
        if(!MHFSCL.Module._mhfs_cl_track_seek_to_pcm_frame(that.ptr, pcmFrameIndex)) throw("Failed to seek to " + pcmFrameIndex);
    };

    that.seekSecs = function(floatseconds) {
        that.seek(Math.floor(floatseconds * that.sampleRate));
    };

    that._openPictureIfExists = function() {
        if(!that.picture) {
            return undefined;
        }
        that.picture.hash = MHFSCL.Module._mhfs_cl_djb2(that.picture.pPicture, that.picture.pictureSize);
        that.picture.toURL = function() {
            const srcData = new Uint8Array(MHFSCL.Module.HEAPU8.buffer, that.picture.pPicture, that.picture.pictureSize);
            const picData = new Uint8Array(srcData);
            const blobert = new Blob([picData.buffer], {
                'type' : that.picture.mime
            });
            const url = URL.createObjectURL(blobert);
            return url;
        };
        return that.picture;
    };

    // allocate memory for the mhfs_cl_track and return data
    const alignedTrackSize = MHFSCL.AlignedSize(MHFSCL.mhfs_cl_track.sizeof);
    that.ptr = MHFSCL.Module._malloc(alignedTrackSize + MHFSCL.mhfs_cl_track_return_data.sizeof);
    if(!that.ptr) throw("failed malloc");
    const rd = MHFSCL.mhfs_cl_track_return_data.from(that.ptr + alignedTrackSize);
    const thatid = MHFSCLObjectMap.addData(that);
    const pFullFilename = MHFSCL.Module.stringToNewUTF8(theURL);
    let pMime;
    try {
        // initialize the track
        let start = 0;
        const firstreq = await that._downloadChunk(start, gsignal);
        const mime = firstreq.headers['Content-Type'] || '';
        pMime = MHFSCL.Module.stringToNewUTF8(mime);
        const totalPCMFrames = BigInt(firstreq.headers['X-MHFS-totalPCMFrameCount'] || 0);
        MHFSCL.Module._mhfs_cl_track_init(that.ptr, that.CHUNKSIZE);
        that.initialized = true;
        that._storeChunk(firstreq, start);

        // load enough of the track that the metadata loads
        for(;;) {
            that.picture = null;
            const code = MHFSCL.Module._mhfs_cl_track_load_metadata(that.ptr, rd.ptr, pMime, pFullFilename, totalPCMFrames, MHFSCL.pMHFSCLTrackOnMeta, thatid);
            if(code === MHFSCL.MHFS_CL_SUCCESS) {
                break;
            }
            if(code !== MHFSCL.MHFS_CL_NEED_MORE_DATA){
                that.close();
                throw("Failed opening MHFSCLTrack");
            }
            start = rd.get('needed_offset');
            await that.downloadAndStoreChunk(start, gsignal);
        }
    }
    catch(error) {
        that.close();
        throw(error);
    }
    finally {
        MHFSCL.Module._free(pFullFilename);
        if(pMime) MHFSCL.Module._free(pMime);
        MHFSCLObjectMap.removeData(thatid);
    }

    return that;
};
export { MHFSCLTrack };

const MHFSCLAllocation = function(size) {
    const that = {};
    that.size = 0;
    that.ptr = 0;

    // return a ptr to a block of memory of at least sz bytes
    that.with = function(sz) {
        if(sz <= that.size) {
            return that.ptr;
        }
        const ptr = MHFSCL.Module._realloc(that.ptr, sz);
        if(!ptr) {
            throw("realloc failed");
        }
        that.ptr = ptr;
        that.size = sz;
        return ptr;
    };
    that.free = function() {
        if(that.ptr) {
            MHFSCL.Module._free(that.ptr);
            that.ptr = 0;
            that.size = 0;
        }
    };

    that.with(size);
    return that;
};

// allocates size bytes for each item. creates array of ptrs to point to the data
// [[ptr0, ptr1, ptr...][data0][data1][data...]]
const MHFSCLArrsAlloc = function(nitems, size) {
    const that = {};
    that.nitems = nitems;
    that.ptrarrsize = nitems * MHFSCL.PTRSIZE;
    that.size = 0;

    that.free = function() {
        if(that.alloc) {
            that.alloc.free();
            that.alloc = null;
        }
        that.nitems = 0;
        that.ptrarrsize = 0;
        that.size = 0;
    };

    that.setptrs = function(size) {
        const myarr = new Uint32Array(MHFSCL.Module.HEAPU8.buffer, that.alloc.ptr, that.nitems);
        let dataptr = that.alloc.ptr + that.ptrarrsize;
        for( let i = 0; i < that.nitems; i++) {
            myarr[i] = dataptr;
            dataptr += size;
        }
    };

    that.with = function(sz) {
        sz = MHFSCL.AlignedSize(sz);
        if(that.alloc && (sz <= that.size)) {
            return that.alloc.ptr;
        }
        that.alloc = MHFSCLAllocation(that.ptrarrsize + (nitems * sz));
        that.size = sz;
        that.setptrs(sz);
        return that.alloc.ptr;
    };
    that.with(size);
    return that;
};

const MHFSCLDecoder = async function(outputSampleRate, outputChannelCount) {
    if(!MHFSCL.ready) {
        console.log('MHFSCLDecoder, waiting for MHFSCL to be ready');
        await waitForEvent(MHFSCL, 'ready');
    }
	const that = {};
	that.ptr = MHFSCL.Module._mhfs_cl_decoder_open(outputSampleRate, outputChannelCount, outputSampleRate);
    if(! that.ptr) throw("Failed to open decoder");

    that.outputSampleRate = outputSampleRate;
    that.outputChannelCount = outputChannelCount;
    that.f32_size = 4;
    that.pcm_float_frame_size = that.f32_size * that.outputChannelCount;

    that.returnDataAlloc = MHFSCLAllocation(MHFSCL.mhfs_cl_track_return_data.sizeof);
    that.deinterleaveDataAlloc = MHFSCLArrsAlloc(outputChannelCount, that.outputSampleRate*that.f32_size);
    //that.DM = DownloadManager(262144);
    that.rd = MHFSCL.mhfs_cl_track_return_data.from(that.returnDataAlloc.ptr);

    that.flush = async function() {
        MHFSCL.Module._mhfs_cl_decoder_flush(that.ptr);
    };

    that.closeCurrentTrack = async function() {
        if(that.track) {
            that.track.close();
            that.track = null;
        }
    };

    that.close = async function(){
        await that.closeCurrentTrack();
        MHFSCL.Module._mhfs_cl_decoder_close(that.ptr);
        that.ptr = 0;
        that.returnDataAlloc.free();
        that.deinterleaveDataAlloc.free();
    };
    
    // modifies track
    that.openTrack = async function(signal, intrack, starttime) {
        let doseek = starttime;
        do {
            const url = intrack.url;
            if(that.track) {
                if(that.track.url === url) {
                    doseek = 1;
                    break;
                }
                await that.track.close();
                that.track = null;
                if(signal.aborted) {
                    throw("abort after closing track");
                }                                
            }
            that.track = await MHFSCLTrack(signal, url, that.DM);
        } while(0);

        if(doseek) {
            that.track.seekSecs(starttime);
        }

        if(signal.aborted) {
            console.log('');
            await that.track.close();
            that.track = null;
            throw("abort after open track success");
        }

        return { duration : that.track.duration, mediametadata : that.track.mediametadata };
    };
	
	that.seek_input_pcm_frames = async function(pcmFrameIndex) {
        if(!that.track) throw("nothing to seek on");
        that.track.seek(pcmFrameIndex);
    };

    that.seek = async function(floatseconds) {
        if(!that.track) throw("nothing to seek on");
        that.track.seekSecs(floatseconds);
    }

    that.read_pcm_frames_f32_deinterleaved = async function(todec, destdata, mysignal) {
              
        while(1) {              
            // attempt to decode the samples
            const code = MHFSCL.Module._mhfs_cl_decoder_read_pcm_frames_f32_deinterleaved(that.ptr, that.track.ptr, todec, destdata, that.rd.ptr);

            // success, retdata is frames read
            if(code === MHFSCL.MHFS_CL_SUCCESS)
            {
                return that.rd.get('frames_read');
            }
            if(code !== MHFSCL.MHFS_CL_NEED_MORE_DATA)
            {
                throw("mhfs_cl_decoder_read_pcm_frames_f32_deinterleaved failed");
            }

            // download more data
            await that.track.downloadAndStoreChunk(that.rd.get('needed_offset'), mysignal);
        }        
    };

    that.read_pcm_frames_f32_AudioBuffer = async function(todec, mysignal) {
        let theerror;
        let returnval;
        const destdata = that.deinterleaveDataAlloc.with(todec*that.f32_size);
        try {
            const frames = await that.read_pcm_frames_f32_deinterleaved(todec, destdata, mysignal);
            if(frames) {
                const audiobuffer = new AudioBuffer({'length' : frames, 'numberOfChannels' : that.outputChannelCount, 'sampleRate' : that.outputSampleRate});
                const chanPtrs = new Uint32Array(MHFSCL.Module.HEAPU8.buffer, destdata, that.outputChannelCount);
                for( let i = 0; i < that.outputChannelCount; i++) {
                    const buf = new Float32Array(MHFSCL.Module.HEAPU8.buffer, chanPtrs[i], frames);
                    audiobuffer.copyToChannel(buf, i);
                }
                returnval = audiobuffer;
            }            
        }
        catch(error) {
            theerror = error;
        }
        finally {
            if(theerror) throw(theerror);
            return returnval;
        }        
    };

    that.read_pcm_frames_f32_arrs = async function(todec, mysignal) {
        let theerror;
        let returnval;
        const destdata = that.deinterleaveDataAlloc.with(todec*that.f32_size);
        try {
            const frames = await that.read_pcm_frames_f32_deinterleaved(todec, destdata, mysignal);
            if(frames) {
                const chanPtrs = new Uint32Array(MHFSCL.Module.HEAPU8.buffer, destdata, that.outputChannelCount);
                const obj = { 'length' : frames, 'chanData' : []};
                for( let i = 0; i < that.outputChannelCount; i++) {
                    obj.chanData[i] = new Float32Array(MHFSCL.Module.HEAPU8.buffer, chanPtrs[i], frames);
                }
                returnval = obj;
            }
        }
        catch(error) {
            theerror = error;
        }
        finally {
            if(theerror) throw(theerror);
            return returnval;
        }
    };
	
	return that;
};
export { MHFSCLDecoder };

const ExposeType_LoadTypes = function(BINDTO, wasmMod, loadType) {
    const currentObject = [BINDTO];
    let mainindex = 0;
    BINDTO[wasmMod.UTF8ToString(loadType(mainindex, 1))] = loadType(mainindex, 2); // load ET_TT_CONST_IV)
    while(1) {
        const typeType = loadType(mainindex, 0);
        if(typeType === 0) break;
        if(typeType === BINDTO.ET_TT_CONST_IV) {
            BINDTO[wasmMod.UTF8ToString(loadType(mainindex, 1))] = loadType(mainindex, 2);
        }
        else if(typeType === BINDTO.ET_TT_CONST_CSTRING) {
            BINDTO[wasmMod.UTF8ToString(loadType(mainindex, 1))] = wasmMod.UTF8ToString(loadType(mainindex, 2));
        }
        else if(typeType === BINDTO.ET_TT_ST) {
            const struct = { name: wasmMod.UTF8ToString(loadType(mainindex, 1)), size: loadType(mainindex, 2), members : {}};
            currentObject.push(struct);
        }
        else if(typeType === BINDTO.ET_TT_ST_END) {
            const structmeta = currentObject.pop();
            const structPrototype = {
                members : structmeta.members,
                get : function(memberName) {
                    const ptr = (this.ptr + this.members[memberName].offset);
                    if(this.members[memberName].type === BINDTO.ET_TT_UINT32) {
                        return wasmMod.HEAPU32[ptr  >> 2];
                    }
                    else if(this.members[memberName].type === BINDTO.ET_TT_UINT64) {
                        return BigInt(wasmMod.HEAPU32[ptr  >> 2]) + (BigInt(wasmMod.HEAPU32[(ptr+4)  >> 2]) << BigInt(32));
                    }
                    else if(this.members[memberName].type === BINDTO.ET_TT_UINT16) {
                        return wasmMod.HEAPU16[ptr  >> 1];
                    }
                    else if(this.members[memberName].type === BINDTO.ET_TT_UINT8) {
                        return wasmMod.HEAPU8[ptr];
                    }
                    throw("ENOTIMPLEMENTED");
                }
            };
            const struct = function(ptr) {
                this.ptr = ptr;
            };
            struct.prototype = structPrototype;
            struct.prototype.constructor = struct;
            BINDTO[structmeta.name] = {
                from : (ptr) => new struct(ptr),
                sizeof : structmeta.size
            };
        }
        else if((typeType === BINDTO.ET_TT_UINT64) || (typeType === BINDTO.ET_TT_UINT32) || (typeType === BINDTO.ET_TT_UINT16) || (typeType === BINDTO.ET_TT_UINT8)) {
            currentObject[currentObject.length-1].members[wasmMod.UTF8ToString(loadType(mainindex, 1))] = {
                type : typeType,
                offset : loadType(mainindex, 2)
            };
        }
        mainindex++;
    }
}

Module().then(function(MHFSCLMod){
    MHFSCL.Module = MHFSCLMod;
    MHFSCL.Module.stringToNewUTF8 ||= MHFSCL.Module.allocateUTF8; // < 3.1.35 emcc compat

    // Load types
    ExposeType_LoadTypes(MHFSCL, MHFSCL.Module, MHFSCL.Module._mhfs_cl_et_load_type);

    // link callbacks in
    MHFSCL.pMHFSCLTrackOnMeta = MHFSCL.Module.addFunction(MHFSCLTrackOnMeta, 'viii');

    // finish setup
    MHFSCL.PTRSIZE = 4;

    MHFSCL.AlignedSize = function(size) {
        return Math.ceil(size/4) * 4;
    };

    console.log('MHFSCL is ready!');
    MHFSCL.ready = true;
    if(MHFSCL.on_ready) {
        MHFSCL.on_ready();
    }    
});











( run in 0.938 second using v1.01-cache-2.11-cpan-39bf76dae61 )