Skip to content

Would you like the same but as ts ? #2

@Belrestro

Description

@Belrestro
export class AudioFileRequest {
    private url: string;
    private extension: string;
    private async: boolean = true;

    constructor (url, async) {
        this.url = url;
        this.async = async;
        const splitURL = url.split('.');
        this.extension = splitURL[splitURL.length - 1].toLowerCase();
    }

    onSuccess (decoded) {}

    onFailure (decoded?) {}

    send () {
        if (this.extension !== 'wav' &&
            this.extension !== 'aiff' &&
            this.extension !== 'aif') {
            this.onFailure();
            return;
        }
        const request = new XMLHttpRequest();
        request.open('GET', this.url, this.async);
        request.overrideMimeType('text/plain; charset=x-user-defined');
        request.onreadystatechange = ((event) => {
            if (request.readyState === 4) {
                if (request.status === 200 || request.status === 0) {
                    this.handleResponse(request.responseText);
                } else {
                    this.onFailure();
                }
            }
        }).bind(this);

        request.send(null);
    }

    handleResponse (data) {
        let decoder;
        let decoded;

        if (this.extension === 'wav') {
            decoder = new WAVDecoder();
            decoded = decoder.decode(data);
        } else if (this.extension === 'aiff' || this.extension === 'aif') {
            decoder = new AIFFDecoder();
            decoded = decoder.decode(data);
        }
        this.onSuccess(decoded);
    }
}

export class Decoder {
    getEmptyChunk () {
        return {
            name: '',
            length: 0
        };
    }

    readString (data, offset, length) {
        return data.slice(offset, offset + length);
    }

    readIntL (data, offset, length) {
        let value = 0;
        for (let i = 0; i < length; i++) {
            value = value + ((data.charCodeAt(offset + i) & 0xFF) * Math.pow(2, 8 * i));
        }
        return value;
    }

    readChunkHeaderL (data, offset) {
        const chunk = this.getEmptyChunk();

        chunk.name = this.readString(data, offset, 4);
        chunk.length = this.readIntL(data, offset + 4, 4);
        return chunk;
    }

    readIntB (data, offset, length) {
        let value = 0;
        for (let i = 0; i < length; i++) {
            value = value + ((data.charCodeAt(offset + i) & 0xFF) * Math.pow(2, 8 * (length - i - 1)));
        }
        return value;
    }

    readChunkHeaderB (data, offset) {
        const chunk = this.getEmptyChunk();

        chunk.name = this.readString(data, offset, 4);
        chunk.length = this.readIntB(data, offset + 4, 4);
        return chunk;
    }
    readFloatB (data, offset) {
        let expon = this.readIntB(data, offset, 2);
        const range = 1 << 16 - 1;
        if (expon >= range) {
            expon |= ~(range - 1);
        }

        let sign = 1;
        if (expon < 0) {
            sign = -1;
            expon += range;
        }

        const himant = this.readIntB(data, offset + 2, 4);
        const lomant = this.readIntB(data, offset + 6, 4);
        let value;
        if (expon === himant &&  expon === lomant && lomant === 0) {
            value = 0;
        } else if (expon === 0x7FFF) {
            value = Number.MAX_VALUE;
        } else {
            expon -= 16383;
            value = (himant * 0x100000000 + lomant) * Math.pow(2, expon - 63);
        }
        return sign * value;
    }
}

export class WAVDecoder extends Decoder {
    decode (data) {
        const decoded: any = {};
        let offset = 0;
        // Header
        let chunk = this.readChunkHeaderL(data, offset);
        offset += 8;
        if (chunk.name !== 'RIFF') {
            console.error('File is not a WAV');
            return null;
        }
        let fileLength = chunk.length;
        fileLength += 8;

        const wave = this.readString(data, offset, 4);
        offset += 4;
        if (wave !== 'WAVE') {
            console.error('File is not a WAV');
            return null;
        }
        let bytesPerSample;
        let numberOfChannels;
        let bitDepth;
        let sampleRate;
        let channels;

        while (offset < fileLength) {
            chunk = this.readChunkHeaderL(data, offset);
            offset += 8;
            if (chunk.name === 'fmt ') {
                // File encoding
                const encoding = this.readIntL(data, offset, 2);
                offset += 2;
                if (encoding !== 0x0001) {
                    // Only support PCM
                    console.error('Cannot decode non-PCM encoded WAV file');
                    return null;
                }

                // Number of channels
                numberOfChannels = this.readIntL(data, offset, 2);
                offset += 2;

                // Sample rate
                sampleRate = this.readIntL(data, offset, 4);
                offset += 4;

                // Ignore bytes/sec - 4 bytes
                offset += 4;

                // Ignore block align - 2 bytes
                offset += 2;

                // Bit depth
                bitDepth = this.readIntL(data, offset, 2);
                bytesPerSample = bitDepth / 8;
                offset += 2;
            } else if (chunk.name === 'data') {
                // Data must come after fmt, so we are okay to use it's variables
                 // here
                const length = chunk.length / (bytesPerSample * numberOfChannels);
                channels = [];
                for (let i = 0; i < numberOfChannels; i++) {
                    channels.push(new Float32Array(length));
                }

                for (let i = 0; i < numberOfChannels; i++) {
                    const channel = channels[i];
                    for (let j = 0; j < length; j++) {
                        let index = offset;
                        index += (j * numberOfChannels + i) * bytesPerSample;
                        // Sample
                        let value = this.readIntL(data, index, bytesPerSample);
                        // Scale range from 0 to 2**bitDepth -> -2**(bitDepth-1) to
                        // 2**(bitDepth-1)
                        const range = 1 << bitDepth - 1;
                        if (value >= range) {
                            value |= ~(range - 1);
                        }
                        // Scale range to -1 to 1
                        channel[j] = value / range;
                    }
                }
                offset += chunk.length;
            } else {
                offset += chunk.length;
            }
        }
        decoded.sampleRate = sampleRate;
        decoded.bitDepth = bitDepth;
        decoded.channels = channels;
        decoded.length = length;
        return decoded;
    }
}

export class AIFFDecoder extends Decoder {
    decode (data) {
        const decoded: any = {};
        let offset = 0;
        // Header
        let chunk = this.readChunkHeaderB(data, offset);

        offset += 8;
        if (chunk.name !== 'FORM') {
            console.error('File is not an AIFF');
            return null;
        }

        let fileLength = chunk.length;
        fileLength += 8;

        const aiff = this.readString(data, offset, 4);
        offset += 4;
        if (aiff !== 'AIFF') {
            console.error('File is not an AIFF');
            return null;
        }

        let bytesPerSample;
        let numberOfChannels;
        let bitDepth;
        let sampleRate;
        let channels;

        while (offset < fileLength) {
            chunk = this.readChunkHeaderB(data, offset);
            offset += 8;
            if (chunk.name === 'COMM') {
                // Number of channels
                const numberOfChannels = this.readIntB(data, offset, 2);
                offset += 2;

                // Number of samples
                const length = this.readIntB(data, offset, 4);
                offset += 4;

                channels = [];
                for (let i = 0; i < numberOfChannels; i++) {
                    channels.push(new Float32Array(length));
                }

                // Bit depth
                bitDepth = this.readIntB(data, offset, 2);
                bytesPerSample = bitDepth / 8;
                offset += 2;
                // Sample rate
                sampleRate = this.readFloatB(data, offset);
                offset += 10;
            } else if (chunk.name === 'SSND') {
                // Data offset
                const dataOffset = this.readIntB(data, offset, 4);
                offset += 4;
                // Ignore block size
                offset += 4;
                // Skip over data offset
                offset += dataOffset;
                for (let i = 0; i < numberOfChannels; i++) {
                    const channel = channels[i];
                    for (let j = 0; j < length; j++) {
                        let index = offset;
                        index += (j * numberOfChannels + i) * bytesPerSample;
                        // Sample
                        let value = this.readIntB(data, index, bytesPerSample);
                        // Scale range from 0 to 2**bitDepth -> -2**(bitDepth-1) to
                        // 2**(bitDepth-1)
                        let range = 1 << bitDepth - 1;
                        if (value >= range) {
                            value |= ~(range - 1);
                        }
                        // Scale range to -1 to 1
                        channel[j] = value / range;
                    }
                }
                offset += chunk.length - dataOffset - 8;
            } else {
                offset += chunk.length;
            }
        }
        decoded.sampleRate = sampleRate;
        decoded.bitDepth = bitDepth;
        decoded.channels = channels;
        decoded.length = length;
        return decoded;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions