Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Would you like the same but as ts ? #2

Open
Belrestro opened this issue Feb 9, 2018 · 0 comments
Open

Would you like the same but as ts ? #2

Belrestro opened this issue Feb 9, 2018 · 0 comments

Comments

@Belrestro
Copy link

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;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant