// Copyright (C) 2013 Massachusetts Institute of Technology // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License version 2, // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // WAVFile.js // Utility class for reading and decoding WAV file metadata // Based directly on John Maloney's AS version for the Scratch Flash Player var WAVFile = function() {}; WAVFile.decode = function(waveData) { // Decode the given WAV file data and return an Object with the format and sample data. var result = {}; var data = new OffsetBuffer(waveData); // read WAVE File Header if (data.readString(4) != 'RIFF') { console.log("WAVFile: bad file header"); return; } var totalSize = data.readInt(); if (data.getLength() != (totalSize + 8)) console.log("WAVFile: bad RIFF size; ignoring"); if (data.readString(4) != 'WAVE') { console.log("WAVFile: not a WAVE file"); return; } // read format chunk var formatChunk = WAVFile.extractChunk('fmt ', data); if (formatChunk.getLength() < 16) { console.log("WAVFile: format chunk is too small"); return; } var encoding = formatChunk.readShort(); result.encoding = encoding; result.channels = formatChunk.readShort(); result.samplesPerSecond = formatChunk.readInt(); result.bytesPerSecond = formatChunk.readInt(); result.blockAlignment = formatChunk.readShort(); result.bitsPerSample = formatChunk.readShort(); // get size of data chunk var sampleDataStartAndSize = WAVFile.dataChunkStartAndSize(data); result.sampleDataStart = sampleDataStartAndSize[0]; result.sampleDataSize = sampleDataStartAndSize[1]; // handle various encodings if (encoding == 1) { if (!((result.bitsPerSample == 8) || (result.bitsPerSample == 16))) { console.log("WAVFile: can only handle 8-bit or 16-bit uncompressed PCM data"); return; } result.sampleCount = result.sampleDataSize / 2; } else if (encoding == 17) { if (formatChunk.length < 20) { console.log("WAVFile: adpcm format chunk is too small"); return; } if (result.channels != 1) { console.log("WAVFile: adpcm supports only one channel (monophonic)"); return; } formatChunk.offset += 2; // skip extra header byte count var samplesPerBlock = formatChunk.readShort(); result.adpcmBlockSize = ((samplesPerBlock - 1) / 2) + 4; // block size in bytes var factChunk = WAVFile.extractChunk('fact', data); if ((factChunk != null) && (factChunk.getLength() == 4)) { result.sampleCount = factChunk.readInt(); } else { // this should never happen, since there should always be a 'fact' chunk // slight over-estimate (doesn't take ADPCM headers into account) result.sampleCount = 2 * result.sampleDataSize; } } else { console.log("WAVFile: unknown encoding " + encoding); return; } return result; }; WAVFile.extractChunk = function(desiredType, data) { // Return the contents of the first chunk of the given type or an empty OffsetBuffer if it is not found. data.offset = 12; while (data.bytesAvailable() > 8) { var chunkType = data.readString(4); var chunkSize = data.readUint(); if (chunkType == desiredType) { if (chunkSize > data.bytesAvailable()) return null; var result = new OffsetBuffer(data.readBytes(chunkSize)); return result; } else { data.offset += chunkSize; } } return new OffsetBuffer(new ArrayBuffer()); }; WAVFile.dataChunkStartAndSize = function(data) { // Return an array with the starting offset and size of the first chunk of the given type. data.offset = 12; while (data.bytesAvailable() >= 8) { var chunkType = data.readString(4); var chunkSize = data.readUint(); if (chunkType == 'data') { if (chunkSize > data.bytesAvailable()) return [0, 0]; // bad wave file return [data.offset, chunkSize]; } else { data.offset += chunkSize; } } return [0, 0]; // chunk not found; bad wave file };