793 lines
27 KiB
JavaScript
793 lines
27 KiB
JavaScript
|
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||
|
exports.FLACDemuxer = require('./src/demuxer');
|
||
|
exports.FLACDecoder = require('./src/decoder');
|
||
|
require('./src/ogg');
|
||
|
|
||
|
},{"./src/decoder":2,"./src/demuxer":3,"./src/ogg":4}],2:[function(require,module,exports){
|
||
|
/*
|
||
|
* FLAC.js - Free Lossless Audio Codec decoder in JavaScript
|
||
|
* Original C version from FFmpeg (c) 2003 Alex Beregszaszi
|
||
|
* JavaScript port by Devon Govett and Jens Nockert of Official.fm Labs
|
||
|
*
|
||
|
* Licensed under the same terms as the original. The original
|
||
|
* license follows.
|
||
|
*
|
||
|
* FLAC.js is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* FLAC.js 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
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
var AV = (window.AV);
|
||
|
|
||
|
var FLACDecoder = AV.Decoder.extend(function() {
|
||
|
AV.Decoder.register('flac', this);
|
||
|
|
||
|
this.prototype.setCookie = function(cookie) {
|
||
|
this.cookie = cookie;
|
||
|
|
||
|
// initialize arrays
|
||
|
this.decoded = [];
|
||
|
for (var i = 0; i < this.format.channelsPerFrame; i++) {
|
||
|
this.decoded[i] = new Int32Array(cookie.maxBlockSize);
|
||
|
}
|
||
|
|
||
|
// for 24 bit lpc frames, this is used to simulate a 64 bit int
|
||
|
this.lpc_total = new Int32Array(2);
|
||
|
};
|
||
|
|
||
|
const BLOCK_SIZES = new Int16Array([
|
||
|
0, 192, 576 << 0, 576 << 1, 576 << 2, 576 << 3, 0, 0,
|
||
|
256 << 0, 256 << 1, 256 << 2, 256 << 3, 256 << 4, 256 << 5, 256 << 6, 256 << 7
|
||
|
]);
|
||
|
|
||
|
const SAMPLE_RATES = new Int32Array([
|
||
|
0, 88200, 176400, 192000,
|
||
|
8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000,
|
||
|
0, 0, 0, 0
|
||
|
]);
|
||
|
|
||
|
const SAMPLE_SIZES = new Int8Array([
|
||
|
0, 8, 12, 0, 16, 20, 24, 0
|
||
|
]);
|
||
|
|
||
|
const MAX_CHANNELS = 8,
|
||
|
CHMODE_INDEPENDENT = 0,
|
||
|
CHMODE_LEFT_SIDE = 8,
|
||
|
CHMODE_RIGHT_SIDE = 9,
|
||
|
CHMODE_MID_SIDE = 10;
|
||
|
|
||
|
this.prototype.readChunk = function() {
|
||
|
var stream = this.bitstream;
|
||
|
if (!stream.available(32))
|
||
|
return;
|
||
|
|
||
|
// frame sync code
|
||
|
if ((stream.read(15) & 0x7FFF) !== 0x7FFC)
|
||
|
throw new Error('Invalid sync code');
|
||
|
|
||
|
var isVarSize = stream.read(1), // variable block size stream code
|
||
|
bsCode = stream.read(4), // block size
|
||
|
srCode = stream.read(4), // sample rate code
|
||
|
chMode = stream.read(4), // channel mode
|
||
|
bpsCode = stream.read(3); // bits per sample
|
||
|
|
||
|
stream.advance(1); // reserved bit
|
||
|
|
||
|
// channels
|
||
|
this.chMode = chMode;
|
||
|
var channels;
|
||
|
|
||
|
if (chMode < MAX_CHANNELS) {
|
||
|
channels = chMode + 1;
|
||
|
this.chMode = CHMODE_INDEPENDENT;
|
||
|
} else if (chMode <= CHMODE_MID_SIDE) {
|
||
|
channels = 2;
|
||
|
} else {
|
||
|
throw new Error('Invalid channel mode');
|
||
|
}
|
||
|
|
||
|
if (channels !== this.format.channelsPerFrame)
|
||
|
throw new Error('Switching channel layout mid-stream not supported.');
|
||
|
|
||
|
// bits per sample
|
||
|
if (bpsCode === 3 || bpsCode === 7)
|
||
|
throw new Error('Invalid sample size code');
|
||
|
|
||
|
this.bps = SAMPLE_SIZES[bpsCode];
|
||
|
if (this.bps !== this.format.bitsPerChannel)
|
||
|
throw new Error('Switching bits per sample mid-stream not supported.');
|
||
|
|
||
|
// sample number or frame number
|
||
|
// see http://www.hydrogenaudio.org/forums/index.php?s=ea7085ffe6d57132c36e6105c0d434c9&showtopic=88390&pid=754269&st=0&#entry754269
|
||
|
var ones = 0;
|
||
|
while (stream.read(1) === 1)
|
||
|
ones++;
|
||
|
|
||
|
var frame_or_sample_num = stream.read(7 - ones);
|
||
|
for (; ones > 1; ones--) {
|
||
|
stream.advance(2); // == 2
|
||
|
frame_or_sample_num = (frame_or_sample_num << 6) | stream.read(6);
|
||
|
}
|
||
|
|
||
|
// block size
|
||
|
if (bsCode === 0)
|
||
|
throw new Error('Reserved blocksize code');
|
||
|
else if (bsCode === 6)
|
||
|
this.blockSize = stream.read(8) + 1;
|
||
|
else if (bsCode === 7)
|
||
|
this.blockSize = stream.read(16) + 1;
|
||
|
else
|
||
|
this.blockSize = BLOCK_SIZES[bsCode];
|
||
|
|
||
|
// sample rate
|
||
|
var sampleRate;
|
||
|
if (srCode < 12)
|
||
|
sampleRate = SAMPLE_RATES[srCode];
|
||
|
else if (srCode === 12)
|
||
|
sampleRate = stream.read(8) * 1000;
|
||
|
else if (srCode === 13)
|
||
|
sampleRate = stream.read(16);
|
||
|
else if (srCode === 14)
|
||
|
sampleRate = stream.read(16) * 10;
|
||
|
else
|
||
|
throw new Error('Invalid sample rate code');
|
||
|
|
||
|
stream.advance(8); // skip CRC check
|
||
|
|
||
|
// subframes
|
||
|
for (var i = 0; i < channels; i++)
|
||
|
this.decodeSubframe(i);
|
||
|
|
||
|
stream.align();
|
||
|
stream.advance(16); // skip CRC frame footer
|
||
|
|
||
|
var is32 = this.bps > 16,
|
||
|
output = new ArrayBuffer(this.blockSize * channels * (is32 ? 4 : 2)),
|
||
|
buf = is32 ? new Int32Array(output) : new Int16Array(output),
|
||
|
blockSize = this.blockSize,
|
||
|
decoded = this.decoded,
|
||
|
j = 0;
|
||
|
|
||
|
switch (this.chMode) {
|
||
|
case CHMODE_INDEPENDENT:
|
||
|
for (var k = 0; k < blockSize; k++) {
|
||
|
for (var i = 0; i < channels; i++) {
|
||
|
buf[j++] = decoded[i][k];
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CHMODE_LEFT_SIDE:
|
||
|
for (var i = 0; i < blockSize; i++) {
|
||
|
var left = decoded[0][i],
|
||
|
right = decoded[1][i];
|
||
|
|
||
|
buf[j++] = left;
|
||
|
buf[j++] = (left - right);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CHMODE_RIGHT_SIDE:
|
||
|
for (var i = 0; i < blockSize; i++) {
|
||
|
var left = decoded[0][i],
|
||
|
right = decoded[1][i];
|
||
|
|
||
|
buf[j++] = (left + right);
|
||
|
buf[j++] = right;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CHMODE_MID_SIDE:
|
||
|
for (var i = 0; i < blockSize; i++) {
|
||
|
var left = decoded[0][i],
|
||
|
right = decoded[1][i];
|
||
|
|
||
|
left -= right >> 1;
|
||
|
buf[j++] = (left + right);
|
||
|
buf[j++] = left;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return buf;
|
||
|
};
|
||
|
|
||
|
this.prototype.decodeSubframe = function(channel) {
|
||
|
var wasted = 0,
|
||
|
stream = this.bitstream,
|
||
|
blockSize = this.blockSize,
|
||
|
decoded = this.decoded;
|
||
|
|
||
|
this.curr_bps = this.bps;
|
||
|
if (channel === 0) {
|
||
|
if (this.chMode === CHMODE_RIGHT_SIDE)
|
||
|
this.curr_bps++;
|
||
|
} else {
|
||
|
if (this.chMode === CHMODE_LEFT_SIDE || this.chMode === CHMODE_MID_SIDE)
|
||
|
this.curr_bps++;
|
||
|
}
|
||
|
|
||
|
if (stream.read(1))
|
||
|
throw new Error("Invalid subframe padding");
|
||
|
|
||
|
var type = stream.read(6);
|
||
|
|
||
|
if (stream.read(1)) {
|
||
|
wasted = 1;
|
||
|
while (!stream.read(1))
|
||
|
wasted++;
|
||
|
|
||
|
this.curr_bps -= wasted;
|
||
|
}
|
||
|
|
||
|
if (this.curr_bps > 32)
|
||
|
throw new Error("decorrelated bit depth > 32 (" + this.curr_bps + ")");
|
||
|
|
||
|
if (type === 0) {
|
||
|
var tmp = stream.read(this.curr_bps, true);
|
||
|
for (var i = 0; i < blockSize; i++)
|
||
|
decoded[channel][i] = tmp;
|
||
|
|
||
|
} else if (type === 1) {
|
||
|
var bps = this.curr_bps;
|
||
|
for (var i = 0; i < blockSize; i++)
|
||
|
decoded[channel][i] = stream.read(bps, true);
|
||
|
|
||
|
} else if ((type >= 8) && (type <= 12)) {
|
||
|
this.decode_subframe_fixed(channel, type & ~0x8);
|
||
|
|
||
|
} else if (type >= 32) {
|
||
|
this.decode_subframe_lpc(channel, (type & ~0x20) + 1);
|
||
|
|
||
|
} else {
|
||
|
throw new Error("Invalid coding type");
|
||
|
}
|
||
|
|
||
|
if (wasted) {
|
||
|
for (var i = 0; i < blockSize; i++)
|
||
|
decoded[channel][i] <<= wasted;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.prototype.decode_subframe_fixed = function(channel, predictor_order) {
|
||
|
var decoded = this.decoded[channel],
|
||
|
stream = this.bitstream,
|
||
|
bps = this.curr_bps;
|
||
|
|
||
|
// warm up samples
|
||
|
for (var i = 0; i < predictor_order; i++)
|
||
|
decoded[i] = stream.read(bps, true);
|
||
|
|
||
|
this.decode_residuals(channel, predictor_order);
|
||
|
|
||
|
var a = 0, b = 0, c = 0, d = 0;
|
||
|
|
||
|
if (predictor_order > 0)
|
||
|
a = decoded[predictor_order - 1];
|
||
|
|
||
|
if (predictor_order > 1)
|
||
|
b = a - decoded[predictor_order - 2];
|
||
|
|
||
|
if (predictor_order > 2)
|
||
|
c = b - decoded[predictor_order - 2] + decoded[predictor_order - 3];
|
||
|
|
||
|
if (predictor_order > 3)
|
||
|
d = c - decoded[predictor_order - 2] + 2 * decoded[predictor_order - 3] - decoded[predictor_order - 4];
|
||
|
|
||
|
switch (predictor_order) {
|
||
|
case 0:
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
case 2:
|
||
|
case 3:
|
||
|
case 4:
|
||
|
var abcd = new Int32Array([a, b, c, d]),
|
||
|
blockSize = this.blockSize;
|
||
|
|
||
|
for (var i = predictor_order; i < blockSize; i++) {
|
||
|
abcd[predictor_order - 1] += decoded[i];
|
||
|
|
||
|
for (var j = predictor_order - 2; j >= 0; j--) {
|
||
|
abcd[j] += abcd[j + 1];
|
||
|
}
|
||
|
|
||
|
decoded[i] = abcd[0];
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new Error("Invalid Predictor Order " + predictor_order);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.prototype.decode_subframe_lpc = function(channel, predictor_order) {
|
||
|
var stream = this.bitstream,
|
||
|
decoded = this.decoded[channel],
|
||
|
bps = this.curr_bps,
|
||
|
blockSize = this.blockSize;
|
||
|
|
||
|
// warm up samples
|
||
|
for (var i = 0; i < predictor_order; i++) {
|
||
|
decoded[i] = stream.read(bps, true);
|
||
|
}
|
||
|
|
||
|
var coeff_prec = stream.read(4) + 1;
|
||
|
if (coeff_prec === 16)
|
||
|
throw new Error("Invalid coefficient precision");
|
||
|
|
||
|
var qlevel = stream.read(5, true);
|
||
|
if (qlevel < 0)
|
||
|
throw new Error("Negative qlevel, maybe buggy stream");
|
||
|
|
||
|
var coeffs = new Int32Array(32);
|
||
|
for (var i = 0; i < predictor_order; i++) {
|
||
|
coeffs[i] = stream.read(coeff_prec, true);
|
||
|
}
|
||
|
|
||
|
this.decode_residuals(channel, predictor_order);
|
||
|
|
||
|
if (this.bps <= 16) {
|
||
|
for (var i = predictor_order; i < blockSize - 1; i += 2) {
|
||
|
var d = decoded[i - predictor_order],
|
||
|
s0 = 0, s1 = 0, c = 0;
|
||
|
|
||
|
for (var j = predictor_order - 1; j > 0; j--) {
|
||
|
c = coeffs[j];
|
||
|
s0 += c * d;
|
||
|
d = decoded[i - j];
|
||
|
s1 += c * d;
|
||
|
}
|
||
|
|
||
|
c = coeffs[0];
|
||
|
s0 += c * d;
|
||
|
d = decoded[i] += (s0 >> qlevel);
|
||
|
s1 += c * d;
|
||
|
decoded[i + 1] += (s1 >> qlevel);
|
||
|
}
|
||
|
|
||
|
if (i < blockSize) {
|
||
|
var sum = 0;
|
||
|
for (var j = 0; j < predictor_order; j++)
|
||
|
sum += coeffs[j] * decoded[i - j - 1];
|
||
|
|
||
|
decoded[i] += (sum >> qlevel);
|
||
|
}
|
||
|
} else {
|
||
|
// simulate 64 bit integer using an array of two 32 bit ints
|
||
|
var total = this.lpc_total;
|
||
|
for (var i = predictor_order; i < blockSize; i++) {
|
||
|
// reset total to 0
|
||
|
total[0] = 0;
|
||
|
total[1] = 0;
|
||
|
|
||
|
for (j = 0; j < predictor_order; j++) {
|
||
|
// simulate `total += coeffs[j] * decoded[i - j - 1]`
|
||
|
multiply_add(total, coeffs[j], decoded[i - j - 1]);
|
||
|
}
|
||
|
|
||
|
// simulate `decoded[i] += total >> qlevel`
|
||
|
// we know that qlevel < 32 since it is a 5 bit field (see above)
|
||
|
decoded[i] += (total[0] >>> qlevel) | (total[1] << (32 - qlevel));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const TWO_PWR_32_DBL = Math.pow(2, 32);
|
||
|
|
||
|
// performs `total += a * b` on a simulated 64 bit int
|
||
|
// total is an Int32Array(2)
|
||
|
// a and b are JS numbers (32 bit ints)
|
||
|
function multiply_add(total, a, b) {
|
||
|
// multiply a * b (we can use normal JS multiplication for this)
|
||
|
var r = a * b;
|
||
|
var n = r < 0;
|
||
|
if (n)
|
||
|
r = -r;
|
||
|
|
||
|
var r_low = (r % TWO_PWR_32_DBL) | 0;
|
||
|
var r_high = (r / TWO_PWR_32_DBL) | 0;
|
||
|
if (n) {
|
||
|
r_low = ~r_low + 1;
|
||
|
r_high = ~r_high;
|
||
|
}
|
||
|
|
||
|
// add result to total
|
||
|
var a48 = total[1] >>> 16;
|
||
|
var a32 = total[1] & 0xFFFF;
|
||
|
var a16 = total[0] >>> 16;
|
||
|
var a00 = total[0] & 0xFFFF;
|
||
|
|
||
|
var b48 = r_high >>> 16;
|
||
|
var b32 = r_high & 0xFFFF;
|
||
|
var b16 = r_low >>> 16;
|
||
|
var b00 = r_low & 0xFFFF;
|
||
|
|
||
|
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
|
||
|
c00 += a00 + b00;
|
||
|
c16 += c00 >>> 16;
|
||
|
c00 &= 0xFFFF;
|
||
|
c16 += a16 + b16;
|
||
|
c32 += c16 >>> 16;
|
||
|
c16 &= 0xFFFF;
|
||
|
c32 += a32 + b32;
|
||
|
c48 += c32 >>> 16;
|
||
|
c32 &= 0xFFFF;
|
||
|
c48 += a48 + b48;
|
||
|
c48 &= 0xFFFF;
|
||
|
|
||
|
// store result back in total
|
||
|
total[0] = (c16 << 16) | c00;
|
||
|
total[1] = (c48 << 16) | c32;
|
||
|
}
|
||
|
|
||
|
const INT_MAX = 32767;
|
||
|
|
||
|
this.prototype.decode_residuals = function(channel, predictor_order) {
|
||
|
var stream = this.bitstream,
|
||
|
method_type = stream.read(2);
|
||
|
|
||
|
if (method_type > 1)
|
||
|
throw new Error('Illegal residual coding method ' + method_type);
|
||
|
|
||
|
var rice_order = stream.read(4),
|
||
|
samples = (this.blockSize >>> rice_order);
|
||
|
|
||
|
if (predictor_order > samples)
|
||
|
throw new Error('Invalid predictor order ' + predictor_order + ' > ' + samples);
|
||
|
|
||
|
var decoded = this.decoded[channel],
|
||
|
sample = predictor_order,
|
||
|
i = predictor_order;
|
||
|
|
||
|
for (var partition = 0; partition < (1 << rice_order); partition++) {
|
||
|
var tmp = stream.read(method_type === 0 ? 4 : 5);
|
||
|
|
||
|
if (tmp === (method_type === 0 ? 15 : 31)) {
|
||
|
tmp = stream.read(5);
|
||
|
for (; i < samples; i++)
|
||
|
decoded[sample++] = stream.read(tmp, true);
|
||
|
|
||
|
} else {
|
||
|
for (; i < samples; i++)
|
||
|
decoded[sample++] = this.golomb(tmp, INT_MAX, 0);
|
||
|
}
|
||
|
|
||
|
i = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const MIN_CACHE_BITS = 25;
|
||
|
|
||
|
this.prototype.golomb = function(k, limit, esc_len) {
|
||
|
var data = this.bitstream,
|
||
|
offset = data.bitPosition,
|
||
|
buf = data.peek(32 - offset) << offset,
|
||
|
v = 0;
|
||
|
|
||
|
var log = 31 - clz(buf | 1); // log2(buf)
|
||
|
|
||
|
if (log - k >= 32 - MIN_CACHE_BITS && 32 - log < limit) {
|
||
|
buf >>>= log - k;
|
||
|
buf += (30 - log) << k;
|
||
|
|
||
|
data.advance(32 + k - log);
|
||
|
v = buf;
|
||
|
|
||
|
} else {
|
||
|
for (var i = 0; data.read(1) === 0; i++)
|
||
|
buf = data.peek(32 - offset) << offset;
|
||
|
|
||
|
if (i < limit - 1) {
|
||
|
if (k)
|
||
|
buf = data.read(k);
|
||
|
else
|
||
|
buf = 0;
|
||
|
|
||
|
v = buf + (i << k);
|
||
|
|
||
|
} else if (i === limit - 1) {
|
||
|
buf = data.read(esc_len);
|
||
|
v = buf + 1;
|
||
|
|
||
|
} else {
|
||
|
v = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (v >> 1) ^ -(v & 1);
|
||
|
};
|
||
|
|
||
|
// Should be in the damned standard library...
|
||
|
function clz(input) {
|
||
|
var output = 0,
|
||
|
curbyte = 0;
|
||
|
|
||
|
while(true) { // emulate goto in JS using the break statement :D
|
||
|
curbyte = input >>> 24;
|
||
|
if (curbyte) break;
|
||
|
output += 8;
|
||
|
|
||
|
curbyte = input >>> 16;
|
||
|
if (curbyte & 0xff) break;
|
||
|
output += 8;
|
||
|
|
||
|
curbyte = input >>> 8;
|
||
|
if (curbyte & 0xff) break;
|
||
|
output += 8;
|
||
|
|
||
|
curbyte = input;
|
||
|
if (curbyte & 0xff) break;
|
||
|
output += 8;
|
||
|
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
if (!(curbyte & 0xf0))
|
||
|
output += 4;
|
||
|
else
|
||
|
curbyte >>>= 4;
|
||
|
|
||
|
if (curbyte & 0x8)
|
||
|
return output;
|
||
|
|
||
|
if (curbyte & 0x4)
|
||
|
return output + 1;
|
||
|
|
||
|
if (curbyte & 0x2)
|
||
|
return output + 2;
|
||
|
|
||
|
if (curbyte & 0x1)
|
||
|
return output + 3;
|
||
|
|
||
|
// shouldn't get here
|
||
|
return output + 4;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
module.exports = FLACDecoder;
|
||
|
|
||
|
},{}],3:[function(require,module,exports){
|
||
|
/*
|
||
|
* FLAC.js - Free Lossless Audio Codec decoder in JavaScript
|
||
|
* By Devon Govett and Jens Nockert of Official.fm Labs
|
||
|
*
|
||
|
* FLAC.js is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* FLAC.js 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
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
var AV = (window.AV);
|
||
|
|
||
|
var FLACDemuxer = AV.Demuxer.extend(function() {
|
||
|
AV.Demuxer.register(this);
|
||
|
|
||
|
this.probe = function(buffer) {
|
||
|
return buffer.peekString(0, 4) === 'fLaC';
|
||
|
}
|
||
|
|
||
|
const STREAMINFO = 0,
|
||
|
PADDING = 1,
|
||
|
APPLICATION = 2,
|
||
|
SEEKTABLE = 3,
|
||
|
VORBIS_COMMENT = 4,
|
||
|
CUESHEET = 5,
|
||
|
PICTURE = 6,
|
||
|
INVALID = 127,
|
||
|
STREAMINFO_SIZE = 34;
|
||
|
|
||
|
this.prototype.readChunk = function() {
|
||
|
var stream = this.stream;
|
||
|
|
||
|
if (!this.readHeader && stream.available(4)) {
|
||
|
if (stream.readString(4) !== 'fLaC')
|
||
|
return this.emit('error', 'Invalid FLAC file.');
|
||
|
|
||
|
this.readHeader = true;
|
||
|
}
|
||
|
|
||
|
while (stream.available(1) && !this.last) {
|
||
|
if (!this.readBlockHeaders) {
|
||
|
var tmp = stream.readUInt8();
|
||
|
this.last = (tmp & 0x80) === 0x80,
|
||
|
this.type = tmp & 0x7F,
|
||
|
this.size = stream.readUInt24();
|
||
|
}
|
||
|
|
||
|
if (!this.foundStreamInfo && this.type !== STREAMINFO)
|
||
|
return this.emit('error', 'STREAMINFO must be the first block');
|
||
|
|
||
|
if (!stream.available(this.size))
|
||
|
return;
|
||
|
|
||
|
switch (this.type) {
|
||
|
case STREAMINFO:
|
||
|
if (this.foundStreamInfo)
|
||
|
return this.emit('error', 'STREAMINFO can only occur once.');
|
||
|
|
||
|
if (this.size !== STREAMINFO_SIZE)
|
||
|
return this.emit('error', 'STREAMINFO size is wrong.');
|
||
|
|
||
|
this.foundStreamInfo = true;
|
||
|
var bitstream = new AV.Bitstream(stream);
|
||
|
|
||
|
var cookie = {
|
||
|
minBlockSize: bitstream.read(16),
|
||
|
maxBlockSize: bitstream.read(16),
|
||
|
minFrameSize: bitstream.read(24),
|
||
|
maxFrameSize: bitstream.read(24)
|
||
|
};
|
||
|
|
||
|
this.format = {
|
||
|
formatID: 'flac',
|
||
|
sampleRate: bitstream.read(20),
|
||
|
channelsPerFrame: bitstream.read(3) + 1,
|
||
|
bitsPerChannel: bitstream.read(5) + 1
|
||
|
};
|
||
|
|
||
|
this.emit('format', this.format);
|
||
|
this.emit('cookie', cookie);
|
||
|
|
||
|
var sampleCount = bitstream.read(36);
|
||
|
this.emit('duration', sampleCount / this.format.sampleRate * 1000 | 0);
|
||
|
|
||
|
stream.advance(16); // skip MD5 hashes
|
||
|
this.readBlockHeaders = false;
|
||
|
break;
|
||
|
|
||
|
/*
|
||
|
I am only looking at the least significant 32 bits of sample number and offset data
|
||
|
This is more than sufficient for the longest flac file I have (~50 mins 2-channel 16-bit 44.1k which uses about 7.5% of the UInt32 space for the largest offset)
|
||
|
Can certainly be improved by storing sample numbers and offests as doubles, but would require additional overriding of the searchTimestamp and seek functions (possibly more?)
|
||
|
Also the flac faq suggests it would be possible to find frame lengths and thus create seek points on the fly via decoding but I assume this would be slow
|
||
|
I may look into these thigns though as my project progresses
|
||
|
*/
|
||
|
case SEEKTABLE:
|
||
|
for(var s=0; s<this.size/18; s++)
|
||
|
{
|
||
|
if(stream.peekUInt32(0) == 0xFFFFFFFF && stream.peekUInt32(1) == 0xFFFFFFFF)
|
||
|
{
|
||
|
//placeholder, ignore
|
||
|
stream.advance(18);
|
||
|
} else {
|
||
|
if(stream.readUInt32() > 0)
|
||
|
{
|
||
|
this.emit('error', 'Seek points with sample number >UInt32 not supported');
|
||
|
}
|
||
|
var samplenum = stream.readUInt32();
|
||
|
if(stream.readUInt32() > 0)
|
||
|
{
|
||
|
this.emit('error', 'Seek points with stream offset >UInt32 not supported');
|
||
|
}
|
||
|
var offset = stream.readUInt32();
|
||
|
|
||
|
stream.advance(2);
|
||
|
|
||
|
this.addSeekPoint(offset, samplenum);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case VORBIS_COMMENT:
|
||
|
// see http://www.xiph.org/vorbis/doc/v-comment.html
|
||
|
this.metadata || (this.metadata = {});
|
||
|
var len = stream.readUInt32(true);
|
||
|
|
||
|
this.metadata.vendor = stream.readString(len);
|
||
|
var length = stream.readUInt32(true);
|
||
|
|
||
|
for (var i = 0; i < length; i++) {
|
||
|
len = stream.readUInt32(true);
|
||
|
var str = stream.readString(len, 'utf8'),
|
||
|
idx = str.indexOf('=');
|
||
|
|
||
|
this.metadata[str.slice(0, idx).toLowerCase()] = str.slice(idx + 1);
|
||
|
}
|
||
|
|
||
|
// TODO: standardize field names across formats
|
||
|
break;
|
||
|
|
||
|
case PICTURE:
|
||
|
var type = stream.readUInt32();
|
||
|
if (type !== 3) { // make sure this is album art (type 3)
|
||
|
stream.advance(this.size - 4);
|
||
|
} else {
|
||
|
var mimeLen = stream.readUInt32(),
|
||
|
mime = stream.readString(mimeLen),
|
||
|
descLen = stream.readUInt32(),
|
||
|
description = stream.readString(descLen),
|
||
|
width = stream.readUInt32(),
|
||
|
height = stream.readUInt32(),
|
||
|
depth = stream.readUInt32(),
|
||
|
colors = stream.readUInt32(),
|
||
|
length = stream.readUInt32(),
|
||
|
picture = stream.readBuffer(length);
|
||
|
|
||
|
this.metadata || (this.metadata = {});
|
||
|
this.metadata.coverArt = picture;
|
||
|
}
|
||
|
|
||
|
// does anyone want the rest of the info?
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
stream.advance(this.size);
|
||
|
this.readBlockHeaders = false;
|
||
|
}
|
||
|
|
||
|
if (this.last && this.metadata)
|
||
|
this.emit('metadata', this.metadata);
|
||
|
}
|
||
|
|
||
|
while (stream.available(1) && this.last) {
|
||
|
var buffer = stream.readSingleBuffer(stream.remainingBytes());
|
||
|
this.emit('data', buffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
module.exports = FLACDemuxer;
|
||
|
|
||
|
},{}],4:[function(require,module,exports){
|
||
|
var AV = (window.AV);
|
||
|
|
||
|
// if ogg.js exists, register a plugin
|
||
|
try {
|
||
|
var OggDemuxer = (window.AV.OggDemuxer);
|
||
|
} catch (e) {};
|
||
|
if (!OggDemuxer) return;
|
||
|
|
||
|
OggDemuxer.plugins.push({
|
||
|
magic: "\177FLAC",
|
||
|
|
||
|
init: function() {
|
||
|
this.list = new AV.BufferList();
|
||
|
this.stream = new AV.Stream(this.list);
|
||
|
},
|
||
|
|
||
|
readHeaders: function(packet) {
|
||
|
var stream = this.stream;
|
||
|
this.list.append(new AV.Buffer(packet));
|
||
|
|
||
|
stream.advance(5); // magic
|
||
|
if (stream.readUInt8() != 1)
|
||
|
throw new Error('Unsupported FLAC version');
|
||
|
|
||
|
stream.advance(3);
|
||
|
if (stream.peekString(0, 4) != 'fLaC')
|
||
|
throw new Error('Not flac');
|
||
|
|
||
|
this.flac = AV.Demuxer.find(stream.peekSingleBuffer(0, stream.remainingBytes()));
|
||
|
if (!this.flac)
|
||
|
throw new Error('Flac demuxer not found');
|
||
|
|
||
|
this.flac.prototype.readChunk.call(this);
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
readPacket: function(packet) {
|
||
|
this.list.append(new AV.Buffer(packet));
|
||
|
this.flac.prototype.readChunk.call(this);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
},{}]},{},[1])
|
||
|
|
||
|
|
||
|
//# sourceMappingURL=flac.js.map
|