lamejs
Version:
Pure JavaScript MP3 Encoder
971 lines (836 loc) • 31.5 kB
JavaScript
var common = require('./common.js');
var System = common.System;
var VbrMode = common.VbrMode;
var Float = common.Float;
var ShortBlock = common.ShortBlock;
var Util = common.Util;
var Arrays = common.Arrays;
var new_array_n = common.new_array_n;
var new_byte = common.new_byte;
var new_double = common.new_double;
var new_float = common.new_float;
var new_float_n = common.new_float_n;
var new_int = common.new_int;
var new_int_n = common.new_int_n;
var assert = common.assert;
/**
* A Vbr header may be present in the ancillary data field of the first frame of
* an mp3 bitstream<BR>
* The Vbr header (optionally) contains
* <UL>
* <LI>frames total number of audio frames in the bitstream
* <LI>bytes total number of bytes in the bitstream
* <LI>toc table of contents
* </UL>
*
* toc (table of contents) gives seek points for random access.<BR>
* The ith entry determines the seek point for i-percent duration.<BR>
* seek point in bytes = (toc[i]/256.0) * total_bitstream_bytes<BR>
* e.g. half duration seek point = (toc[50]/256.0) * total_bitstream_bytes
*/
VBRTag.NUMTOCENTRIES = 100;
VBRTag.MAXFRAMESIZE = 2880;
function VBRTag() {
var lame;
var bs;
var v;
this.setModules = function (_lame, _bs, _v) {
lame = _lame;
bs = _bs;
v = _v;
};
var FRAMES_FLAG = 0x0001;
var BYTES_FLAG = 0x0002;
var TOC_FLAG = 0x0004;
var VBR_SCALE_FLAG = 0x0008;
var NUMTOCENTRIES = VBRTag.NUMTOCENTRIES;
/**
* (0xB40) the max freeformat 640 32kHz framesize.
*/
var MAXFRAMESIZE = VBRTag.MAXFRAMESIZE;
/**
* <PRE>
* 4 bytes for Header Tag
* 4 bytes for Header Flags
* 100 bytes for entry (toc)
* 4 bytes for frame size
* 4 bytes for stream size
* 4 bytes for VBR scale. a VBR quality indicator: 0=best 100=worst
* 20 bytes for LAME tag. for example, "LAME3.12 (beta 6)"
* ___________
* 140 bytes
* </PRE>
*/
var VBRHEADERSIZE = (NUMTOCENTRIES + 4 + 4 + 4 + 4 + 4);
var LAMEHEADERSIZE = (VBRHEADERSIZE + 9 + 1 + 1 + 8
+ 1 + 1 + 3 + 1 + 1 + 2 + 4 + 2 + 2);
/**
* The size of the Xing header MPEG-1, bit rate in kbps.
*/
var XING_BITRATE1 = 128;
/**
* The size of the Xing header MPEG-2, bit rate in kbps.
*/
var XING_BITRATE2 = 64;
/**
* The size of the Xing header MPEG-2.5, bit rate in kbps.
*/
var XING_BITRATE25 = 32;
/**
* ISO-8859-1 charset for byte to string operations.
*/
var ISO_8859_1 = null; //Charset.forName("ISO-8859-1");
/**
* VBR header magic string.
*/
var VBRTag0 = "Xing";
/**
* VBR header magic string (VBR == VBRMode.vbr_off).
*/
var VBRTag1 = "Info";
/**
* Lookup table for fast CRC-16 computation. Uses the polynomial
* x^16+x^15+x^2+1
*/
var crc16Lookup = [0x0000, 0xC0C1, 0xC181, 0x0140,
0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741,
0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41,
0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40,
0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941,
0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40,
0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341,
0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141,
0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740,
0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40,
0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41,
0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940,
0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,
0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541,
0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340,
0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141,
0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740,
0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40,
0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41,
0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940,
0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41,
0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541,
0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340,
0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140,
0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741,
0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41,
0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40,
0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941,
0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40,
0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540,
0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341,
0x4100, 0x81C1, 0x8081, 0x4040];
/***********************************************************************
* Robert Hegemann 2001-01-17
***********************************************************************/
function addVbr(v, bitrate) {
v.nVbrNumFrames++;
v.sum += bitrate;
v.seen++;
if (v.seen < v.want) {
return;
}
if (v.pos < v.size) {
v.bag[v.pos] = v.sum;
v.pos++;
v.seen = 0;
}
if (v.pos == v.size) {
for (var i = 1; i < v.size; i += 2) {
v.bag[i / 2] = v.bag[i];
}
v.want *= 2;
v.pos /= 2;
}
}
function xingSeekTable(v, t) {
if (v.pos <= 0)
return;
for (var i = 1; i < NUMTOCENTRIES; ++i) {
var j = i / NUMTOCENTRIES, act, sum;
var indx = 0 | (Math.floor(j * v.pos));
if (indx > v.pos - 1)
indx = v.pos - 1;
act = v.bag[indx];
sum = v.sum;
var seek_point = 0 | (256. * act / sum);
if (seek_point > 255)
seek_point = 255;
t[i] = 0xff & seek_point;
}
}
/**
* Add VBR entry, used to fill the VBR TOC entries.
*
* @param gfp
* global flags
*/
this.addVbrFrame = function (gfp) {
var gfc = gfp.internal_flags;
var kbps = Tables.bitrate_table[gfp.version][gfc.bitrate_index];
assert(gfc.VBR_seek_table.bag != null);
addVbr(gfc.VBR_seek_table, kbps);
}
/**
* Read big endian integer (4-bytes) from header.
*
* @param buf
* header containing the integer
* @param bufPos
* offset into the header
* @return extracted integer
*/
function extractInteger(buf, bufPos) {
var x = buf[bufPos + 0] & 0xff;
x <<= 8;
x |= buf[bufPos + 1] & 0xff;
x <<= 8;
x |= buf[bufPos + 2] & 0xff;
x <<= 8;
x |= buf[bufPos + 3] & 0xff;
return x;
}
/**
* Write big endian integer (4-bytes) in the header.
*
* @param buf
* header to write the integer into
* @param bufPos
* offset into the header
* @param value
* integer value to write
*/
function createInteger(buf, bufPos, value) {
buf[bufPos + 0] = 0xff & ((value >> 24) & 0xff);
buf[bufPos + 1] = 0xff & ((value >> 16) & 0xff);
buf[bufPos + 2] = 0xff & ((value >> 8) & 0xff);
buf[bufPos + 3] = 0xff & (value & 0xff);
}
/**
* Write big endian short (2-bytes) in the header.
*
* @param buf
* header to write the integer into
* @param bufPos
* offset into the header
* @param value
* integer value to write
*/
function createShort(buf, bufPos, value) {
buf[bufPos + 0] = 0xff & ((value >> 8) & 0xff);
buf[bufPos + 1] = 0xff & (value & 0xff);
}
/**
* Check for magic strings (Xing/Info).
*
* @param buf
* header to check
* @param bufPos
* header offset to check
* @return magic string found
*/
function isVbrTag(buf, bufPos) {
return new String(buf, bufPos, VBRTag0.length(), ISO_8859_1)
.equals(VBRTag0)
|| new String(buf, bufPos, VBRTag1.length(), ISO_8859_1)
.equals(VBRTag1);
}
function shiftInBitsValue(x, n, v) {
return 0xff & ((x << n) | (v & ~(-1 << n)));
}
/**
* Construct the MP3 header using the settings of the global flags.
*
* <img src="1000px-Mp3filestructure.svg.png">
*
* @param gfp
* global flags
* @param buffer
* header
*/
function setLameTagFrameHeader(gfp, buffer) {
var gfc = gfp.internal_flags;
// MP3 Sync Word
buffer[0] = shiftInBitsValue(buffer[0], 8, 0xff);
buffer[1] = shiftInBitsValue(buffer[1], 3, 7);
buffer[1] = shiftInBitsValue(buffer[1], 1,
(gfp.out_samplerate < 16000) ? 0 : 1);
// Version
buffer[1] = shiftInBitsValue(buffer[1], 1, gfp.version);
// 01 == Layer 3
buffer[1] = shiftInBitsValue(buffer[1], 2, 4 - 3);
// Error protection
buffer[1] = shiftInBitsValue(buffer[1], 1, (!gfp.error_protection) ? 1
: 0);
// Bit rate
buffer[2] = shiftInBitsValue(buffer[2], 4, gfc.bitrate_index);
// Frequency
buffer[2] = shiftInBitsValue(buffer[2], 2, gfc.samplerate_index);
// Pad. Bit
buffer[2] = shiftInBitsValue(buffer[2], 1, 0);
// Priv. Bit
buffer[2] = shiftInBitsValue(buffer[2], 1, gfp.extension);
// Mode
buffer[3] = shiftInBitsValue(buffer[3], 2, gfp.mode.ordinal());
// Mode extension (Used with Joint Stereo)
buffer[3] = shiftInBitsValue(buffer[3], 2, gfc.mode_ext);
// Copy
buffer[3] = shiftInBitsValue(buffer[3], 1, gfp.copyright);
// Original
buffer[3] = shiftInBitsValue(buffer[3], 1, gfp.original);
// Emphasis
buffer[3] = shiftInBitsValue(buffer[3], 2, gfp.emphasis);
/* the default VBR header. 48 kbps layer III, no padding, no crc */
/* but sampling freq, mode and copyright/copy protection taken */
/* from first valid frame */
buffer[0] = 0xff;
var abyte = 0xff & (buffer[1] & 0xf1);
var bitrate;
if (1 == gfp.version) {
bitrate = XING_BITRATE1;
} else {
if (gfp.out_samplerate < 16000)
bitrate = XING_BITRATE25;
else
bitrate = XING_BITRATE2;
}
if (gfp.VBR == VbrMode.vbr_off)
bitrate = gfp.brate;
var bbyte;
if (gfp.free_format)
bbyte = 0x00;
else
bbyte = 0xff & (16 * lame.BitrateIndex(bitrate, gfp.version,
gfp.out_samplerate));
/*
* Use as much of the info from the real frames in the Xing header:
* samplerate, channels, crc, etc...
*/
if (gfp.version == 1) {
/* MPEG1 */
buffer[1] = 0xff & (abyte | 0x0a);
/* was 0x0b; */
abyte = 0xff & (buffer[2] & 0x0d);
/* AF keep also private bit */
buffer[2] = 0xff & (bbyte | abyte);
/* 64kbs MPEG1 frame */
} else {
/* MPEG2 */
buffer[1] = 0xff & (abyte | 0x02);
/* was 0x03; */
abyte = 0xff & (buffer[2] & 0x0d);
/* AF keep also private bit */
buffer[2] = 0xff & (bbyte | abyte);
/* 64kbs MPEG2 frame */
}
}
/**
* Get VBR tag information
*
* @param buf
* header to analyze
* @param bufPos
* offset into the header
* @return VBR tag data
*/
this.getVbrTag = function (buf) {
var pTagData = new VBRTagData();
var bufPos = 0;
/* get Vbr header data */
pTagData.flags = 0;
/* get selected MPEG header data */
var hId = (buf[bufPos + 1] >> 3) & 1;
var hSrIndex = (buf[bufPos + 2] >> 2) & 3;
var hMode = (buf[bufPos + 3] >> 6) & 3;
var hBitrate = ((buf[bufPos + 2] >> 4) & 0xf);
hBitrate = Tables.bitrate_table[hId][hBitrate];
/* check for FFE syncword */
if ((buf[bufPos + 1] >> 4) == 0xE)
pTagData.samprate = Tables.samplerate_table[2][hSrIndex];
else
pTagData.samprate = Tables.samplerate_table[hId][hSrIndex];
/* determine offset of header */
if (hId != 0) {
/* mpeg1 */
if (hMode != 3)
bufPos += (32 + 4);
else
bufPos += (17 + 4);
} else {
/* mpeg2 */
if (hMode != 3)
bufPos += (17 + 4);
else
bufPos += (9 + 4);
}
if (!isVbrTag(buf, bufPos))
return null;
bufPos += 4;
pTagData.hId = hId;
/* get flags */
var head_flags = pTagData.flags = extractInteger(buf, bufPos);
bufPos += 4;
if ((head_flags & FRAMES_FLAG) != 0) {
pTagData.frames = extractInteger(buf, bufPos);
bufPos += 4;
}
if ((head_flags & BYTES_FLAG) != 0) {
pTagData.bytes = extractInteger(buf, bufPos);
bufPos += 4;
}
if ((head_flags & TOC_FLAG) != 0) {
if (pTagData.toc != null) {
for (var i = 0; i < NUMTOCENTRIES; i++)
pTagData.toc[i] = buf[bufPos + i];
}
bufPos += NUMTOCENTRIES;
}
pTagData.vbrScale = -1;
if ((head_flags & VBR_SCALE_FLAG) != 0) {
pTagData.vbrScale = extractInteger(buf, bufPos);
bufPos += 4;
}
pTagData.headersize = ((hId + 1) * 72000 * hBitrate)
/ pTagData.samprate;
bufPos += 21;
var encDelay = buf[bufPos + 0] << 4;
encDelay += buf[bufPos + 1] >> 4;
var encPadding = (buf[bufPos + 1] & 0x0F) << 8;
encPadding += buf[bufPos + 2] & 0xff;
/* check for reasonable values (this may be an old Xing header, */
/* not a INFO tag) */
if (encDelay < 0 || encDelay > 3000)
encDelay = -1;
if (encPadding < 0 || encPadding > 3000)
encPadding = -1;
pTagData.encDelay = encDelay;
pTagData.encPadding = encPadding;
/* success */
return pTagData;
}
/**
* Initializes the header
*
* @param gfp
* global flags
*/
this.InitVbrTag = function (gfp) {
var gfc = gfp.internal_flags;
/**
* <PRE>
* Xing VBR pretends to be a 48kbs layer III frame. (at 44.1kHz).
* (at 48kHz they use 56kbs since 48kbs frame not big enough for
* table of contents)
* let's always embed Xing header inside a 64kbs layer III frame.
* this gives us enough room for a LAME version string too.
* size determined by sampling frequency (MPEG1)
* 32kHz: 216 bytes@48kbs 288bytes@ 64kbs
* 44.1kHz: 156 bytes 208bytes@64kbs (+1 if padding = 1)
* 48kHz: 144 bytes 192
*
* MPEG 2 values are the same since the framesize and samplerate
* are each reduced by a factor of 2.
* </PRE>
*/
var kbps_header;
if (1 == gfp.version) {
kbps_header = XING_BITRATE1;
} else {
if (gfp.out_samplerate < 16000)
kbps_header = XING_BITRATE25;
else
kbps_header = XING_BITRATE2;
}
if (gfp.VBR == VbrMode.vbr_off)
kbps_header = gfp.brate;
// make sure LAME Header fits into Frame
var totalFrameSize = ((gfp.version + 1) * 72000 * kbps_header)
/ gfp.out_samplerate;
var headerSize = (gfc.sideinfo_len + LAMEHEADERSIZE);
gfc.VBR_seek_table.TotalFrameSize = totalFrameSize;
if (totalFrameSize < headerSize || totalFrameSize > MAXFRAMESIZE) {
/* disable tag, it wont fit */
gfp.bWriteVbrTag = false;
return;
}
gfc.VBR_seek_table.nVbrNumFrames = 0;
gfc.VBR_seek_table.nBytesWritten = 0;
gfc.VBR_seek_table.sum = 0;
gfc.VBR_seek_table.seen = 0;
gfc.VBR_seek_table.want = 1;
gfc.VBR_seek_table.pos = 0;
if (gfc.VBR_seek_table.bag == null) {
gfc.VBR_seek_table.bag = new int[400];
gfc.VBR_seek_table.size = 400;
}
// write dummy VBR tag of all 0's into bitstream
var buffer = new_byte(MAXFRAMESIZE);
setLameTagFrameHeader(gfp, buffer);
var n = gfc.VBR_seek_table.TotalFrameSize;
for (var i = 0; i < n; ++i) {
bs.add_dummy_byte(gfp, buffer[i] & 0xff, 1);
}
}
/**
* Fast CRC-16 computation (uses table crc16Lookup).
*
* @param value
* @param crc
* @return
*/
function crcUpdateLookup(value, crc) {
var tmp = crc ^ value;
crc = (crc >> 8) ^ crc16Lookup[tmp & 0xff];
return crc;
}
this.updateMusicCRC = function (crc, buffer, bufferPos, size) {
for (var i = 0; i < size; ++i)
crc[0] = crcUpdateLookup(buffer[bufferPos + i], crc[0]);
}
/**
* Write LAME info: mini version + info on various switches used (Jonathan
* Dee 2001/08/31).
*
* @param gfp
* global flags
* @param musicLength
* music length
* @param streamBuffer
* pointer to output buffer
* @param streamBufferPos
* offset into the output buffer
* @param crc
* computation of CRC-16 of Lame Tag so far (starting at frame
* sync)
* @return number of bytes written to the stream
*/
function putLameVBR(gfp, musicLength, streamBuffer, streamBufferPos, crc) {
var gfc = gfp.internal_flags;
var bytesWritten = 0;
/* encoder delay */
var encDelay = gfp.encoder_delay;
/* encoder padding */
var encPadding = gfp.encoder_padding;
/* recall: gfp.VBR_q is for example set by the switch -V */
/* gfp.quality by -q, -h, -f, etc */
var quality = (100 - 10 * gfp.VBR_q - gfp.quality);
var version = v.getLameVeryShortVersion();
var vbr;
var revision = 0x00;
var revMethod;
// numbering different in vbr_mode vs. Lame tag
var vbrTypeTranslator = [1, 5, 3, 2, 4, 0, 3];
var lowpass = 0 | (((gfp.lowpassfreq / 100.0) + .5) > 255 ? 255
: (gfp.lowpassfreq / 100.0) + .5);
var peakSignalAmplitude = 0;
var radioReplayGain = 0;
var audiophileReplayGain = 0;
var noiseShaping = gfp.internal_flags.noise_shaping;
var stereoMode = 0;
var nonOptimal = 0;
var sourceFreq = 0;
var misc = 0;
var musicCRC = 0;
// psy model type: Gpsycho or NsPsytune
var expNPsyTune = (gfp.exp_nspsytune & 1) != 0;
var safeJoint = (gfp.exp_nspsytune & 2) != 0;
var noGapMore = false;
var noGapPrevious = false;
var noGapCount = gfp.internal_flags.nogap_total;
var noGapCurr = gfp.internal_flags.nogap_current;
// 4 bits
var athType = gfp.ATHtype;
var flags = 0;
// vbr modes
var abrBitrate;
switch (gfp.VBR) {
case vbr_abr:
abrBitrate = gfp.VBR_mean_bitrate_kbps;
break;
case vbr_off:
abrBitrate = gfp.brate;
break;
default:
abrBitrate = gfp.VBR_min_bitrate_kbps;
}
// revision and vbr method
if (gfp.VBR.ordinal() < vbrTypeTranslator.length)
vbr = vbrTypeTranslator[gfp.VBR.ordinal()];
else
vbr = 0x00; // unknown
revMethod = 0x10 * revision + vbr;
// ReplayGain
if (gfc.findReplayGain) {
if (gfc.RadioGain > 0x1FE)
gfc.RadioGain = 0x1FE;
if (gfc.RadioGain < -0x1FE)
gfc.RadioGain = -0x1FE;
// set name code
radioReplayGain = 0x2000;
// set originator code to `determined automatically'
radioReplayGain |= 0xC00;
if (gfc.RadioGain >= 0) {
// set gain adjustment
radioReplayGain |= gfc.RadioGain;
} else {
// set the sign bit
radioReplayGain |= 0x200;
// set gain adjustment
radioReplayGain |= -gfc.RadioGain;
}
}
// peak sample
if (gfc.findPeakSample)
peakSignalAmplitude = Math
.abs(0 | ((( gfc.PeakSample) / 32767.0) * Math.pow(2, 23) + .5));
// nogap
if (noGapCount != -1) {
if (noGapCurr > 0)
noGapPrevious = true;
if (noGapCurr < noGapCount - 1)
noGapMore = true;
}
// flags
flags = athType + ((expNPsyTune ? 1 : 0) << 4)
+ ((safeJoint ? 1 : 0) << 5) + ((noGapMore ? 1 : 0) << 6)
+ ((noGapPrevious ? 1 : 0) << 7);
if (quality < 0)
quality = 0;
// stereo mode field (Intensity stereo is not implemented)
switch (gfp.mode) {
case MONO:
stereoMode = 0;
break;
case STEREO:
stereoMode = 1;
break;
case DUAL_CHANNEL:
stereoMode = 2;
break;
case JOINT_STEREO:
if (gfp.force_ms)
stereoMode = 4;
else
stereoMode = 3;
break;
case NOT_SET:
//$FALL-THROUGH$
default:
stereoMode = 7;
break;
}
if (gfp.in_samplerate <= 32000)
sourceFreq = 0x00;
else if (gfp.in_samplerate == 48000)
sourceFreq = 0x02;
else if (gfp.in_samplerate > 48000)
sourceFreq = 0x03;
else {
// default is 44100Hz
sourceFreq = 0x01;
}
// Check if the user overrided the default LAME behavior with some
// nasty options
if (gfp.short_blocks == ShortBlock.short_block_forced
|| gfp.short_blocks == ShortBlock.short_block_dispensed
|| ((gfp.lowpassfreq == -1) && (gfp.highpassfreq == -1)) || /* "-k" */
(gfp.scale_left < gfp.scale_right)
|| (gfp.scale_left > gfp.scale_right)
|| (gfp.disable_reservoir && gfp.brate < 320) || gfp.noATH
|| gfp.ATHonly || (athType == 0) || gfp.in_samplerate <= 32000)
nonOptimal = 1;
misc = noiseShaping + (stereoMode << 2) + (nonOptimal << 5)
+ (sourceFreq << 6);
musicCRC = gfc.nMusicCRC;
// Write all this information into the stream
createInteger(streamBuffer, streamBufferPos + bytesWritten, quality);
bytesWritten += 4;
for (var j = 0; j < 9; j++) {
streamBuffer[streamBufferPos + bytesWritten + j] = 0xff & version .charAt(j);
}
bytesWritten += 9;
streamBuffer[streamBufferPos + bytesWritten] = 0xff & revMethod;
bytesWritten++;
streamBuffer[streamBufferPos + bytesWritten] = 0xff & lowpass;
bytesWritten++;
createInteger(streamBuffer, streamBufferPos + bytesWritten,
peakSignalAmplitude);
bytesWritten += 4;
createShort(streamBuffer, streamBufferPos + bytesWritten,
radioReplayGain);
bytesWritten += 2;
createShort(streamBuffer, streamBufferPos + bytesWritten,
audiophileReplayGain);
bytesWritten += 2;
streamBuffer[streamBufferPos + bytesWritten] = 0xff & flags;
bytesWritten++;
if (abrBitrate >= 255)
streamBuffer[streamBufferPos + bytesWritten] = 0xFF;
else
streamBuffer[streamBufferPos + bytesWritten] = 0xff & abrBitrate;
bytesWritten++;
streamBuffer[streamBufferPos + bytesWritten] = 0xff & (encDelay >> 4);
streamBuffer[streamBufferPos + bytesWritten + 1] = 0xff & ((encDelay << 4) + (encPadding >> 8));
streamBuffer[streamBufferPos + bytesWritten + 2] = 0xff & encPadding;
bytesWritten += 3;
streamBuffer[streamBufferPos + bytesWritten] = 0xff & misc;
bytesWritten++;
// unused in rev0
streamBuffer[streamBufferPos + bytesWritten++] = 0;
createShort(streamBuffer, streamBufferPos + bytesWritten, gfp.preset);
bytesWritten += 2;
createInteger(streamBuffer, streamBufferPos + bytesWritten, musicLength);
bytesWritten += 4;
createShort(streamBuffer, streamBufferPos + bytesWritten, musicCRC);
bytesWritten += 2;
// Calculate tag CRC.... must be done here, since it includes previous
// information
for (var i = 0; i < bytesWritten; i++)
crc = crcUpdateLookup(streamBuffer[streamBufferPos + i], crc);
createShort(streamBuffer, streamBufferPos + bytesWritten, crc);
bytesWritten += 2;
return bytesWritten;
}
function skipId3v2(fpStream) {
// seek to the beginning of the stream
fpStream.seek(0);
// read 10 bytes in case there's an ID3 version 2 header here
var id3v2Header = new_byte(10);
fpStream.readFully(id3v2Header);
/* does the stream begin with the ID3 version 2 file identifier? */
var id3v2TagSize;
if (!new String(id3v2Header, "ISO-8859-1").startsWith("ID3")) {
/*
* the tag size (minus the 10-byte header) is encoded into four
* bytes where the most significant bit is clear in each byte
*/
id3v2TagSize = (((id3v2Header[6] & 0x7f) << 21)
| ((id3v2Header[7] & 0x7f) << 14)
| ((id3v2Header[8] & 0x7f) << 7) | (id3v2Header[9] & 0x7f))
+ id3v2Header.length;
} else {
/* no ID3 version 2 tag in this stream */
id3v2TagSize = 0;
}
return id3v2TagSize;
}
this.getLameTagFrame = function (gfp, buffer) {
var gfc = gfp.internal_flags;
if (!gfp.bWriteVbrTag) {
return 0;
}
if (gfc.Class_ID != Lame.LAME_ID) {
return 0;
}
if (gfc.VBR_seek_table.pos <= 0) {
return 0;
}
if (buffer.length < gfc.VBR_seek_table.TotalFrameSize) {
return gfc.VBR_seek_table.TotalFrameSize;
}
Arrays.fill(buffer, 0, gfc.VBR_seek_table.TotalFrameSize, 0);
// 4 bytes frame header
setLameTagFrameHeader(gfp, buffer);
// Create TOC entries
var toc = new_byte(NUMTOCENTRIES);
if (gfp.free_format) {
for (var i = 1; i < NUMTOCENTRIES; ++i)
toc[i] = 0xff & (255 * i / 100);
} else {
xingSeekTable(gfc.VBR_seek_table, toc);
}
// Start writing the tag after the zero frame
var streamIndex = gfc.sideinfo_len;
/**
* Note: Xing header specifies that Xing data goes in the ancillary data
* with NO ERROR PROTECTION. If error protecton in enabled, the Xing
* data still starts at the same offset, and now it is in sideinfo data
* block, and thus will not decode correctly by non-Xing tag aware
* players
*/
if (gfp.error_protection)
streamIndex -= 2;
// Put Vbr tag
if (gfp.VBR == VbrMode.vbr_off) {
buffer[streamIndex++] = 0xff & VBRTag1.charAt(0);
buffer[streamIndex++] = 0xff & VBRTag1.charAt(1);
buffer[streamIndex++] = 0xff & VBRTag1.charAt(2);
buffer[streamIndex++] = 0xff & VBRTag1.charAt(3);
} else {
buffer[streamIndex++] = 0xff & VBRTag0.charAt(0);
buffer[streamIndex++] = 0xff & VBRTag0.charAt(1);
buffer[streamIndex++] = 0xff & VBRTag0.charAt(2);
buffer[streamIndex++] = 0xff & VBRTag0.charAt(3);
}
// Put header flags
createInteger(buffer, streamIndex, FRAMES_FLAG + BYTES_FLAG + TOC_FLAG
+ VBR_SCALE_FLAG);
streamIndex += 4;
// Put Total Number of frames
createInteger(buffer, streamIndex, gfc.VBR_seek_table.nVbrNumFrames);
streamIndex += 4;
// Put total audio stream size, including Xing/LAME Header
var streamSize = (gfc.VBR_seek_table.nBytesWritten + gfc.VBR_seek_table.TotalFrameSize);
createInteger(buffer, streamIndex, 0 | streamSize);
streamIndex += 4;
/* Put TOC */
System.arraycopy(toc, 0, buffer, streamIndex, toc.length);
streamIndex += toc.length;
if (gfp.error_protection) {
// (jo) error_protection: add crc16 information to header
bs.CRC_writeheader(gfc, buffer);
}
// work out CRC so far: initially crc = 0
var crc = 0x00;
for (var i = 0; i < streamIndex; i++)
crc = crcUpdateLookup(buffer[i], crc);
// Put LAME VBR info
streamIndex += putLameVBR(gfp, streamSize, buffer, streamIndex, crc);
return gfc.VBR_seek_table.TotalFrameSize;
}
/**
* Write final VBR tag to the file.
*
* @param gfp
* global flags
* @param stream
* stream to add the VBR tag to
* @return 0 (OK), -1 else
* @throws IOException
* I/O error
*/
this.putVbrTag = function (gfp, stream) {
var gfc = gfp.internal_flags;
if (gfc.VBR_seek_table.pos <= 0)
return -1;
// Seek to end of file
stream.seek(stream.length());
// Get file size, abort if file has zero length.
if (stream.length() == 0)
return -1;
// The VBR tag may NOT be located at the beginning of the stream. If an
// ID3 version 2 tag was added, then it must be skipped to write the VBR
// tag data.
var id3v2TagSize = skipId3v2(stream);
// Seek to the beginning of the stream
stream.seek(id3v2TagSize);
var buffer = new_byte(MAXFRAMESIZE);
var bytes = getLameTagFrame(gfp, buffer);
if (bytes > buffer.length) {
return -1;
}
if (bytes < 1) {
return 0;
}
// Put it all to disk again
stream.write(buffer, 0, bytes);
// success
return 0;
}
}
module.exports = VBRTag;