diff --git a/README.md b/README.md index 47a65ce..098a6ac 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -# wavefile +# prx-wavefile + Copyright (c) 2017-2019 Rafael da Silva Rocha. https://github.com/rochars/wavefile [![NPM version](https://img.shields.io/npm/v/wavefile.svg?style=for-the-badge)](https://www.npmjs.com/package/wavefile) [![Docs](https://img.shields.io/badge/API-docs-blue.svg?style=for-the-badge)](https://rochars.github.io/wavefile/docs) [![Tests](https://img.shields.io/badge/tests-online-blue.svg?style=for-the-badge)](https://rochars.github.io/wavefile/test/browser.html) [![Codecov](https://img.shields.io/codecov/c/github/rochars/wavefile.svg?style=flat-square)](https://codecov.io/gh/rochars/wavefile) [![Unix Build](https://img.shields.io/travis/rochars/wavefile.svg?style=flat-square)](https://travis-ci.org/rochars/wavefile) [![Windows Build](https://img.shields.io/appveyor/ci/rochars/wavefile.svg?style=flat-square&logo=appveyor)](https://ci.appveyor.com/project/rochars/wavefile) [![Scrutinizer](https://img.shields.io/scrutinizer/g/rochars/wavefile.svg?style=flat-square&logo=scrutinizer)](https://scrutinizer-ci.com/g/rochars/wavefile/) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1880/badge)](https://bestpractices.coreinfrastructure.org/projects/1880) - -## Notice -My country, Brazil, is under a fascist government that is hunting and killing its opponents. I've been threatened too. - +# prx-wavefile Create, read and write wav files according to the specs. @@ -19,7 +17,14 @@ Create, read and write wav files according to the specs. - **Handle files up to 2GB** - **Zero dependencies** -With **wavefile** you can: +`prx-wavefile` is a fork of the excellent [wavefile](https://github.com/rochars/wavefile) project created by @rochars. + +This fork also supports these additional features: + +- [Broadcast Wave Format](https://en.wikipedia.org/wiki/Broadcast_Wave_Format) for MPEG audio described in [EBU Tech 3285-s1 - Specification of the Broadcast Wave Format (BWF) - Supplement 1, MPEG Audio - first edition (1997)](http://tech.ebu.ch/docs/tech/tech3285s1.pdf). +- [CartChunk](http://www.cartchunk.org/) metadata for broadcast automation described in [AES46-2002 (s2008): AES standard for network and file transfer of audio Audio-file transfer and exchange Radio traffic audio delivery extension to the broadcast-WAVE-file forma](http://www.aes.org/publications/standards/search.cfm?docID=41) + +With **prx-wavefile** you can: - [Create wav files](#create-wave-files-from-scratch) - [Read wav files](#read-wave-files) @@ -31,15 +36,19 @@ With **wavefile** you can: - [Encode/decode files as ADPCM, A-Law and μ-Law](#ima-adpcm) - [Turn RIFF files to RIFX and RIFX to RIFF](#rifx) - [Create or edit BWF metadata ("bext" chunk)](#add-bwf-metadata) +- [Create wav files from MPEG audio](#create-from-mpeg) +- [Create or edit Cart Chunk metadata ("cart" chunk)](#add-cart-chunk) And more. ## Install + ``` npm install wavefile ``` To use it from the [command line](#command-line), install it globally: + ``` npm install wavefile -g ``` @@ -47,23 +56,30 @@ npm install wavefile -g ## Use ### Node + ```javascript -const wavefile = require('wavefile'); +const wavefile = require("prx-wavefile"); let wav = new wavefile.WaveFile(); ``` -or + +or + ```javascript -const WaveFile = require('wavefile').WaveFile; +const WaveFile = require("prx-wavefile").WaveFile; let wav = new WaveFile(); ``` + or + ```javascript -import { WaveFile } from 'wavefile'; +import { WaveFile } from "prx-wavefile"; let wav = new WaveFile(); ``` ### Browser -Use the **wavefile.js** file in the *dist* folder: + +Use the **wavefile.js** file in the _dist_ folder: + ```html ``` -Or load it from the [jsDelivr](https://cdn.jsdelivr.net/npm/wavefile) CDN: +Or load it from the [jsDelivr](https://cdn.jsdelivr.net/npm/prx-wavefile) CDN: + ```html - + ``` -Or load it from [unpkg](https://unpkg.com/wavefile): +Or load it from [unpkg](https://unpkg.com/prx-wavefile): + ```html - + ``` #### Browser compatibility + IE10+. Should work in all modern browsers. Cross-browser tests powered by - ### Command line use + To see the available options: + ``` wavefile --help ``` ## Node.js Example + ```javascript -const WaveFile = require('wavefile').WaveFile; +const WaveFile = require("prx-wavefile").WaveFile; // Load a wav file buffer as a WaveFile object let wav = new WaveFile(buffer); @@ -115,31 +136,34 @@ let wavDataURI = wav.toDataURI(); ``` ## Table of Contents + - [Install](#install) - [Use](#use) - [Operation Manual](#operation-manual) - * [Create wave files from scratch](#create-wave-files-from-scratch) - * [Read wave files](#read-wave-files) - * [Add RIFF tags to files](#add-riff-tags-to-files) - * [Add cue points to files](#add-cue-points-to-files) - * [Create regions in files](#create-regions-in-files) - * [RIFX](#rifx) - * [IMA-ADPCM](#ima-adpcm) - * [A-Law](#a-law) - * [mu-Law](#mu-law) - * [Change the bit depth](#change-the-bit-depth) - * [Change the sample rate](#change-the-sample-rate) - * [Add BWF metadata](#add-bwf-metadata) - * [RF64](#rf64) - * [XML Chunks](#xml-chunks) - * [The samples](#the-samples) - * [Command line](#command-line) + - [Create wave files from scratch](#create-wave-files-from-scratch) + - [Read wave files](#read-wave-files) + - [Add RIFF tags to files](#add-riff-tags-to-files) + - [Add cue points to files](#add-cue-points-to-files) + - [Create regions in files](#create-regions-in-files) + - [RIFX](#rifx) + - [IMA-ADPCM](#ima-adpcm) + - [A-Law](#a-law) + - [mu-Law](#mu-law) + - [Change the bit depth](#change-the-bit-depth) + - [Change the sample rate](#change-the-sample-rate) + - [Add BWF metadata](#add-bwf-metadata) + - [Create BWF from MPEG](#create-from-mpeg) + - [Cart Chunk](#add-cart-chunk) + - [RF64](#rf64) + - [XML Chunks](#xml-chunks) + - [The samples](#the-samples) + - [Command line](#command-line) - [API](#api) - * [The WaveFile methods:](#the-wavefile-methods-) - * [The WaveFile properties](#the-wavefile-properties) - + [Cue points](#cue-points) - + [Sample loops](#sample-loops) - + [LIST chunk](#list-chunk) + - [The WaveFile methods:](#the-wavefile-methods-) + - [The WaveFile properties](#the-wavefile-properties) + - [Cue points](#cue-points) + - [Sample loops](#sample-loops) + - [LIST chunk](#list-chunk) - [Contributing to wavefile](#contributing-to-wavefile) - [References](#references) - [Legal](#legal) @@ -147,28 +171,33 @@ let wavDataURI = wav.toDataURI(); ## Operation Manual ### Create wave files from scratch -Use the ```fromScratch(numChannels, sampleRate, bitDepth, samples)``` method. + +Use the `fromScratch(numChannels, sampleRate, bitDepth, samples)` method. #### Mono: + ```javascript let wav = new WaveFile(); // Create a mono wave file, 44.1 kHz, 32-bit and 4 samples -wav.fromScratch(1, 44100, '32', [0, -2147483, 2147483, 4]); +wav.fromScratch(1, 44100, "32", [0, -2147483, 2147483, 4]); fs.writeFileSync(path, wav.toBuffer()); ``` #### Stereo: + Samples can be informed interleaved or de-interleaved. If they are de-interleaved, WaveFile will interleave them. In this example they are de-interleaved. + ```javascript // Stereo, 48 kHz, 8-bit, de-interleaved samples // WaveFile interleave the samples automatically -wav.fromScratch(2, 48000, '8', [ - [0, 2, 4, 3], - [0, 1, 4, 3] +wav.fromScratch(2, 48000, "8", [ + [0, 2, 4, 3], + [0, 1, 4, 3] ]); fs.writeFileSync(path, wav.toBuffer()); ``` + Possible values for the bit depth are: "4" - 4-bit IMA-ADPCM "8" - 8-bit @@ -183,11 +212,13 @@ Possible values for the bit depth are: You can also use any bit depth between "8" and "53", like **"11", "12", "17", "20" and so on**. #### A word on bit depth + Resolutions other than 4-bit, 8-bit, 16-bit, 24-bit, 32-bit (integer), 32-bit (fp) and 64-bit (fp) are implemented as WAVE_FORMAT_EXTENSIBLE and may not be supported by some players. ### Read wave files + ```javascript -const WaveFile = require('wavefile').WaveFile; +const WaveFile = require("prx-wavefile").WaveFile; wav = new WaveFile(); // Read a wav file from a buffer wav.fromBuffer(buffer); @@ -198,37 +229,45 @@ wav.fromDataURI(dataURI); ``` ### Add RIFF tags to files + You can create (or overwrite) tags on files with the **WaveFile.setTag()** method. + ```javascript // Write the ICMT tag with some comments to the file wav.setTag("ICMT", "some comments"); ``` To get the value of a tag (if it exists), use **WaveFile.getTag()**: + ```javascript console.log(wav.getTag("ICMT")); // some comments ``` You can delete a tag with **WaveFile.deleteTag()**: + ```javascript wav.deleteTag("ICMT"); ``` ### Add cue points to files -You can create cue points using the **WaveFile.setCuePoint()** method. The method takes a object with the cue point data and creates a cue point in the corresponding position of the file. The only required attribute of the object is *position*, a number representing the position of the point in milliseconds: + +You can create cue points using the **WaveFile.setCuePoint()** method. The method takes a object with the cue point data and creates a cue point in the corresponding position of the file. The only required attribute of the object is _position_, a number representing the position of the point in milliseconds: + ```javascript // to create a cue point -wav.setCuePoint({position: 1500}); +wav.setCuePoint({ position: 1500 }); ``` -You can also create cue points with labels by defining a *label* attribute: +You can also create cue points with labels by defining a _label_ attribute: + ```javascript // to create a cue point with a label -wav.setCuePoint({position: 1500, label: 'some label'}); +wav.setCuePoint({ position: 1500, label: "some label" }); ``` To delete a cue point use **WaveFile.deleteCuePoint()** informing the index of the point. Points are ordered according to their position. **The first point is indexed as 1.** + ```javascript wav.deleteCuePoint(1); ``` @@ -236,19 +275,22 @@ wav.deleteCuePoint(1); Mind that creating or deleting cue points will change the index of other points if they exist. To list all the cue points in a file, in the order they appear: + ```javascript let cuePoints = wav.listCuePoints(); ``` + This method will return a list with cue points ordered as they appear in the file. + ```javascript [ { position: 500, // the position in milliseconds - label: 'cue marker 1', + label: "cue marker 1", end: 1500, // the end position in milliseconds dwName: 1, dwPosition: 0, - fccChunk: 'data', + fccChunk: "data", dwChunkStart: 0, dwBlockStart: 0, dwSampleOffset: 22050, // the position as a sample offset @@ -257,21 +299,25 @@ This method will return a list with cue points ordered as they appear in the fil dwCountry: 0, dwLanguage: 0, dwDialect: 0, - dwCodePage: 0, - }, + dwCodePage: 0 + } //... ]; ``` ### Create regions in files + You can create regions using the **WaveFile.setCuePoint()** method. Regions are cue points with extra data. -If you define a not null *end* attribute in the object describing the cue point, the point will be created as a region. The *end* attribute should be the end of the region, in milliseconds, counting from the start of the file, and always greater than the *position* of the point: +If you define a not null _end_ attribute in the object describing the cue point, the point will be created as a region. The _end_ attribute should be the end of the region, in milliseconds, counting from the start of the file, and always greater than the _position_ of the point: + ```javascript // to create a region with a label: -wav.setCuePoint({position: 1500, end: 2500, label: 'some label'}); +wav.setCuePoint({ position: 1500, end: 2500, label: "some label" }); ``` + You can also define the following optional properties when creating a region: + - dwPurposeID - dwCountry - dwLanguage @@ -279,12 +325,15 @@ You can also define the following optional properties when creating a region: - dwCodePage ### RIFX -**wavefile** can handle existing RIFX files and create RIFX files from scratch. Files created from scratch will default to RIFF; to create a file as RIFX you must define the container: + +**WaveFile** can handle existing RIFX files and create RIFX files from scratch. Files created from scratch will default to RIFF; to create a file as RIFX you must define the container: + ```javascript -wav.fromScratch(1, 48000, '16', [0, 1, -3278, 327], {"container": "RIFX"}); +wav.fromScratch(1, 48000, "16", [0, 1, -3278, 327], { container: "RIFX" }); ``` RIFX to RIFF and RIFF to RIFX: + ```javascript // Turn a RIFF file to a RIFX file wav.toRIFX(); @@ -294,69 +343,86 @@ wav.toRIFF(); ``` ### IMA-ADPCM + 16-bit 8000 Hz mono wave files can be compressed as IMA-ADPCM: + ```javascript // Encode a 16-bit wave file as 4-bit IMA-ADPCM: wav.toIMAADPCM(); ``` -IMA-ADPCM files compressed with **wavefile** will have a block align of 256 bytes. + +IMA-ADPCM files compressed with **WaveFile** will have a block align of 256 bytes. If the audio is not 16-bit it will be converted to 16-bit before compressing. Compressing audio with sample rate different from 8000 Hz or more than one channel is not supported and will throw errors. To decode 4-bit IMA-ADPCM as 16-bit linear PCM: + ```javascript // Decode 4-bit IMA-ADPCM as 16-bit: wav.fromIMAADPCM(); ``` Decoding always result in 16-bit audio. To decode to another bit depth: + ```javascript // Decode 4-bit IMA-ADPCM as 24-bit: wav.fromIMAADPCM("24"); ``` ### A-Law + 16-bit wave files (mono or stereo) can be encoded as A-Law: + ```javascript // Encode a 16-bit wave file as 8-bit A-law: wav.toALaw(); ``` + If the audio is not 16-bit it will be converted to 16-bit before compressing. To decode 8-bit A-Law as 16-bit linear PCM: + ```javascript // Decode 8-bit A-Law as 16-bit: wav.fromALaw(); ``` Decoding always result in 16-bit audio. To decode to another bit depth: + ```javascript // Decode 8-bit A-Law as 24-bit: wav.fromALaw("24"); ``` ### mu-Law + 16-bit wave files (mono or stereo) can be encoded as mu-Law: + ```javascript // Encode a 16-bit wave file as 8-bit mu-law: wav.toMuLaw(); ``` + If the audio is not 16-bit it will be converted to 16-bit before compressing. To decode 8-bit mu-Law as 16-bit linear PCM: + ```javascript // Decode 8-bit mu-Law as 16-bit: wav.fromMuLaw(); ``` Decoding always result in 16-bit audio. To decode to another bit depth: + ```javascript // Decode 8-bit mu-Law as 24-bit: wav.fromMuLaw("24"); ``` ### Change the bit depth + You can change the bit depth of the audio with the **toBitDepth(bitDepth)** method. WaveFile only change the bit depth of the samples; no dithering is done. + ```javascript // Load a wav file with 32-bit audio let wav = new WaveFile(fs.readFileSync("32bit-file.wav")); @@ -369,7 +435,9 @@ fs.writeFileSync("24bit-file.wav", wav.toBuffer()); ``` ### Change the sample rate + You can change the sample rate of the audio with the **toSampleRate()** method. By default, **cubic interpolation** is used to resample the data. You can choose between **cubic**, **sinc**, **point** and **linear**. + ```javascript // Load a wav file with 16kHz audio let wav = new WaveFile(fs.readFileSync("16kHz-file.wav")); @@ -385,50 +453,58 @@ fs.writeFileSync("44100Hz-file.wav", wav.toBuffer()); ``` To use another method: + ```javascript // Change the sample rate to 44.1kHz using sinc -wav.toSampleRate(44100, {method: "sinc"}); +wav.toSampleRate(44100, { method: "sinc" }); ``` #### Resampling methods + - **point**: Nearest point interpolation, lowest quality, no LPF by default, fastest - **linear**: Linear interpolation, low quality, no LPF by default, fast - **cubic**: Cubic interpolation, use LPF by default **(default method)** - **sinc**: Windowed sinc interpolation, use LPF by default, slowest You can turn the LPF on and off for any resampling method: + ```javascript // Will use 'sinc' method with no LPF -wav.toSampleRate(44100, {method: "sinc", LPF: false}); +wav.toSampleRate(44100, { method: "sinc", LPF: false }); // Will use 'linear' method with LPF -wav.toSampleRate(44100, {method: "linear", LPF: true}); +wav.toSampleRate(44100, { method: "linear", LPF: true }); ``` -The default LPF is a IIR LPF. You may define what type of LPF will be used by changing the LPFType attribute on the *toSampleRate()* param. You can use **IIR** or **FIR**: +The default LPF is a IIR LPF. You may define what type of LPF will be used by changing the LPFType attribute on the _toSampleRate()_ param. You can use **IIR** or **FIR**: + ```javascript // Will use 'linear' method with a FIR LPF -wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'}); +wav.toSampleRate(44100, { method: "linear", LPF: true, LPFType: "FIR" }); // Will use 'linear' method with a IIR LPF, the default -wav.toSampleRate(44100, {method: "linear", LPF: true}); +wav.toSampleRate(44100, { method: "linear", LPF: true }); ``` #### Changing the sample rate of ADPCM, mu-Law or A-Law + You need to convert compressed files to standard PCM before resampling: To resample a mu-Law file: + ```javascript // convert the file to PCM wav.fromMuLaw(); // resample -wav.toSampleRate(44100, {method: "sinc"}); +wav.toSampleRate(44100, { method: "sinc" }); // back to mu-Law wav.toMuLaw(); ``` ### Add BWF metadata + To add BWF data to a file you can use the **bext** property: + ```javascript // Load a wav file with no "bext" let wav = new WaveFile(fs.readFileSync("32bit-file.wav")); @@ -440,15 +516,72 @@ wav.bext.originator = "wavefile"; fs.writeFileSync("32bit-file-with-bext.wav", wav.toBuffer()); ``` -By default **wavefile** will not insert a "bext" chunk in new files or in files that do not already have a "bext" chunk unless a property of **WaveFile.bext** is changed from it's default value. See below the full list of properties in **WaveFile.bext**. +By default **WaveFile** will not insert a "bext" chunk in new files or in files that do not already have a "bext" chunk unless a property of **WaveFile.bext** is changed from it's default value. See below the full list of properties in **WaveFile.bext**. + +### Create From MPEG + +Make a wav file from an mpeg audio file, with optional metadata, using **WaveFile.fromMpeg()**. +There is now an [MPEG reader](lib/mpeg-reader.js) in `prx-wavefile` that is used to +read metadata from the MPEG and automatically set that in "fact", "fmt", "bext", and "mext" chunks. + +The MPEG specific "mext" chunk is specified below in **WaveFile.mext**. + +The optional info is specified below in **WaveFile.mpegInfo**. + +```javascript +// You can create a wav from just an MPEG audio buffer/stream. +let wav = new WaveFile(); +wav.fromMpeg(fs.readFileSync("test.mp2")); + +// You can also pass in the mpeg metadata info if you prefer +// This is specified by WaveFile.mpegInfo +let info = { + version: 1, + layer: 2, + sampleRate: 44100, + bitRate: 128, + channelMode: "stereo", + padding: 1, + modeExtension: 0, + emphasis: 0, + privateBit: 1, + copyright: true, + original: true, + errorProtection: true, + numChannels: 2, + frameSize: 768, + sampleLength: 269568, + freeForm: true +}; +let wav2 = new WaveFile(); +wav2.fromMpeg(fs.readFileSync("test.mp2"), info); +``` + +### Add Cart Chunk + +By default **WaveFile** will not insert a "cart" chunk in new files or in files that do not already have a "cart" chunk unless a property of **WaveFile.cart** is changed from it's default value. See below the full list of properties in **WaveFile.cart**. + +```javascript +let wav = new WaveFile(); +wav.fromMpeg(fs.readFileSync("test.mp2")); + +// Use the wav.cart to set the values +wav.cart.chunkId = "cart"; +wav.cart.cutId = "30000"; +wav.cart.title = "Title"; +wav.cart.artist = "Artist"; +``` ### RF64 -**wavefile** have limited support of RF64 files. It possible to read (at least some) RF64 files, but changing the bit depth or applying compression to the samples will result in a RIFF file. + +**WaveFile** have limited support of RF64 files. It possible to read (at least some) RF64 files, but changing the bit depth or applying compression to the samples will result in a RIFF file. ### XML Chunks -**wavefile** support reading and writing **iXML** and **\_PMX** chunks. + +**Wavefile** support reading and writing **iXML** and **\_PMX** chunks. To get the value of iXML or \_PMX chunks: + ```javascript /** @type {string} */ let iXMLValue = wav.getiXML(); @@ -457,6 +590,7 @@ let _PMXValue = wav.get_PMX(); ``` To set the value of iXML or \_PMX chunks: + ```javascript wav.setiXML(iXMLValue); wav.set_PMX(_PMXValue); @@ -464,16 +598,20 @@ wav.set_PMX(_PMXValue); The value for XML chunks must always be a string. -the *chunkSize* of the XML chunks will be adjusted when *toBuffer()* is called. +the _chunkSize_ of the XML chunks will be adjusted when _toBuffer()_ is called. ### The samples -Samples are stored in *data.samples* as a Uint8Array. -To get the samples as a Float64Array you should use the *getSamples()* method: +Samples are stored in _data.samples_ as a Uint8Array. + +To get the samples as a Float64Array you should use the _getSamples()_ method: + ```javascript let samples = wav.getSamples(); ``` -If the file is stereo or have more than one channel then the samples will be returned de-interleaved in a *Array* of *Float64Array* objects, one Float64Array for each channel. The method takes a optional boolean param *interleaved*, set to **false** by default. If set to **true**, samples will be returned interleaved. **Default is de-interleaved**. + +If the file is stereo or have more than one channel then the samples will be returned de-interleaved in a _Array_ of _Float64Array_ objects, one Float64Array for each channel. The method takes a optional boolean param _interleaved_, set to **false** by default. If set to **true**, samples will be returned interleaved. **Default is de-interleaved**. + ```javascript // Both will return de-interleaved samples samples = wav.getSamples(); @@ -483,7 +621,8 @@ samples = wav.getSamples(false); samples = wav.getSamples(true); ``` -You can use any typed array as the output of *getSamples()*: +You can use any typed array as the output of _getSamples()_: + ```javascript // Will return the samples de-interleaved, // packed in a array of Int32Array objects, one for each channel @@ -498,6 +637,7 @@ let samples = getSamples(true, Int16Array); To get and set samples in a WaveFile instance you should use WaveFile.getSample(index) and WaveFile.setSample(index, sample). The 'index' is the index of the sample in the sample array, not the index of the bytes in data.samples. Example: + ```javascript wav = new WaveFile(); @@ -514,6 +654,7 @@ wav.getSample(1); // return 10, the new value of the second sample ``` ### Range: + - 0 to 255 for 8-bit - -32768 to 32767 for 16-bit - -8388608 to 8388607 for 24-bit @@ -524,17 +665,21 @@ wav.getSample(1); // return 10, the new value of the second sample Floating point samples may be defined out of range. Integer samples will be clamped on overflow. ### Command line + To use **wavefile** from the command line, install it globally: + ``` $ npm install wavefile -g ``` To see the available options: + ``` $ wavefile --help ``` The available options: + ``` --resample Ex: wavefile input.wav --resample=44100 output.wav Change the sample rate. The input file is not affected. @@ -571,14 +716,18 @@ The available options: Show this help page. ``` -The **--resample** command performs resampling using *cubic interpolation* by default. Use it with the **--method** option to change the interpolation method: +The **--resample** command performs resampling using _cubic interpolation_ by default. Use it with the **--method** option to change the interpolation method: + ``` $ wavefile input.wav --resample=44100 method=sinc output.wav ``` -You can use *point*,*linear*,*cubic* and *sinc*. + +You can use _point_,_linear_,_cubic_ and _sinc_. ## API + To create a WaveFile object: + ```javascript // Create a empty WaveFile object WaveFile(); @@ -596,6 +745,7 @@ WaveFile(wav); ``` ### The WaveFile methods + ```javascript /** * Set up the WaveFileCreator object based on the arguments passed. @@ -613,6 +763,14 @@ WaveFile(wav); */ WaveFile.fromScratch(numChannels, sampleRate, bitDepth, samples, options) {} +/** + * Set up the WaveFileCreator object from an mpeg buffer and metadata info. + * @param {!Uint8Array} mpegBuffer The buffer. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ +WaveFile.fromMpeg(mpegBuffer, info=null) {}; + /** * Set up the WaveFileParser object from a byte buffer. * @param {!Uint8Array} wavBuffer The buffer. @@ -776,7 +934,7 @@ WaveFile.listTags() {} * pointData.dwLanguage * pointData.dwDialect * pointData.dwCodePage - * + * * # This is what a complete pointData object look like: * { * position: number, @@ -885,16 +1043,18 @@ WaveFile.set_PMX(_PMXValue) {}; ``` #### WaveFile.listCuePoints() + This method returns a list of objects, each object representing a cue point or region. The list looks like this: + ```javascript [ { position: 500, // the position in milliseconds - label: 'cue marker 1', + label: "cue marker 1", end: 1500, // the end position in milliseconds dwName: 1, dwPosition: 0, - fccChunk: 'data', + fccChunk: "data", dwChunkStart: 0, dwBlockStart: 0, dwSampleOffset: 22050, // the position as a sample offset @@ -904,20 +1064,22 @@ This method returns a list of objects, each object representing a cue point or r dwLanguage: 0, dwDialect: 0, dwCodePage: 0 - }, + } // ... -] +]; ``` + The list order reflects the order of the points in the file. ### The WaveFile properties + ```javascript /** * The container identifier. * "RIFF", "RIFX" and "RF64" are supported. * @type {string} */ -WaveFile.container = ''; +WaveFile.container = ""; /** * @type {number} */ @@ -927,138 +1089,246 @@ WaveFile.chunkSize = 0; * Always 'WAVE'. * @type {string} */ -WaveFile.format = ''; +WaveFile.format = ""; /** * The data of the "fmt" chunk. * @type {!Object} */ WaveFile.fmt = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - audioFormat: 0, - /** @type {number} */ - numChannels: 0, - /** @type {number} */ - sampleRate: 0, - /** @type {number} */ - byteRate: 0, - /** @type {number} */ - blockAlign: 0, - /** @type {number} */ - bitsPerSample: 0, - /** @type {number} */ - cbSize: 0, - /** @type {number} */ - validBitsPerSample: 0, - /** @type {number} */ - dwChannelMask: 0, - /** - * 4 32-bit values representing a 128-bit ID - * @type {!Array} - */ - subformat: [] + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + audioFormat: 0, + /** @type {number} */ + numChannels: 0, + /** @type {number} */ + sampleRate: 0, + /** @type {number} */ + byteRate: 0, + /** @type {number} */ + blockAlign: 0, + /** @type {number} */ + bitsPerSample: 0, + /** @type {number} */ + cbSize: 0, + /** @type {number} */ + validBitsPerSample: 0, + /** @type {number} */ + dwChannelMask: 0, + /** + * 4 32-bit values representing a 128-bit ID + * @type {!Array} + */ + subformat: [] }; /** * The data of the "fact" chunk. * @type {!Object} */ WaveFile.fact = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - dwSampleLength: 0 + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + dwSampleLength: 0 }; /** * The data of the "cue " chunk. * @type {!Object} */ WaveFile.cue = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - dwCuePoints: 0, - /** @type {!Array} */ - points: [], + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + dwCuePoints: 0, + /** @type {!Array} */ + points: [] }; /** * The data of the "smpl" chunk. * @type {!Object} */ WaveFile.smpl = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - dwManufacturer: 0, - /** @type {number} */ - dwProduct: 0, - /** @type {number} */ - dwSamplePeriod: 0, - /** @type {number} */ - dwMIDIUnityNote: 0, - /** @type {number} */ - dwMIDIPitchFraction: 0, - /** @type {number} */ - dwSMPTEFormat: 0, - /** @type {number} */ - dwSMPTEOffset: 0, - /** @type {number} */ - dwNumSampleLoops: 0, - /** @type {number} */ - dwSamplerData: 0, - /** @type {!Array} */ - loops: [], + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + dwManufacturer: 0, + /** @type {number} */ + dwProduct: 0, + /** @type {number} */ + dwSamplePeriod: 0, + /** @type {number} */ + dwMIDIUnityNote: 0, + /** @type {number} */ + dwMIDIPitchFraction: 0, + /** @type {number} */ + dwSMPTEFormat: 0, + /** @type {number} */ + dwSMPTEOffset: 0, + /** @type {number} */ + dwNumSampleLoops: 0, + /** @type {number} */ + dwSamplerData: 0, + /** @type {!Array} */ + loops: [] }; /** * The data of the "bext" chunk. * @type {!Object} */ WaveFile.bext = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {string} */ - description: '', //256 - /** @type {string} */ - originator: '', //32 - /** @type {string} */ - originatorReference: '', //32 - /** @type {string} */ - originationDate: '', //10 - /** @type {string} */ - originationTime: '', //8 - /** - * 2 32-bit values, timeReference high and low - * @type {!Array} - */ - timeReference: [0, 0], - /** @type {number} */ - version: 0, //WORD - /** @type {string} */ - UMID: '', // 64 chars - /** @type {number} */ - loudnessValue: 0, //WORD - /** @type {number} */ - loudnessRange: 0, //WORD - /** @type {number} */ - maxTruePeakLevel: 0, //WORD - /** @type {number} */ - maxMomentaryLoudness: 0, //WORD - /** @type {number} */ - maxShortTermLoudness: 0, //WORD - /** @type {string} */ - reserved: '', //180 - /** @type {string} */ - codingHistory: '' // string, unlimited + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {string} */ + description: "", //256 + /** @type {string} */ + originator: "", //32 + /** @type {string} */ + originatorReference: "", //32 + /** @type {string} */ + originationDate: "", //10 + /** @type {string} */ + originationTime: "", //8 + /** + * 2 32-bit values, timeReference high and low + * @type {!Array} + */ + timeReference: [0, 0], + /** @type {number} */ + version: 0, //WORD + /** @type {string} */ + UMID: "", // 64 chars + /** @type {number} */ + loudnessValue: 0, //WORD + /** @type {number} */ + loudnessRange: 0, //WORD + /** @type {number} */ + maxTruePeakLevel: 0, //WORD + /** @type {number} */ + maxMomentaryLoudness: 0, //WORD + /** @type {number} */ + maxShortTermLoudness: 0, //WORD + /** @type {string} */ + reserved: "", //180 + /** @type {string} */ + codingHistory: "" // string, unlimited +}; +/** + * The data of the 'mext' chunk. + * @type {!Object} + */ +WaveFile.mext = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + soundInformation: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + ancillaryDataLength: 0, + /** @type {number} */ + ancillaryDataDef: 0, //4 + /** @type {string} */ + reserved: "" +}; +/** + * mpegInfo for making a wav from mpeg audio + * @type {!Object} + */ +WaveFile.mpegInfo = { + /** @type {number} */ + version: 0, + /** @type {number} */ + layer: 0, + /** @type {number} */ + sampleRate: 0, + /** @type {number} */ + bitRate: 0, + /** @type {string} */ + channelMode: "", + /** @type {number} */ + padding: 0, + /** @type {number} */ + modeExtension: 0, + /** @type {number} */ + emphasis: 0, + /** @type {number} */ + privateBit: 0, + /** @type {boolean} */ + copyright: false, + /** @type {boolean} */ + original: false, + /** @type {boolean} */ + errorProtection: false, + /** @type {number} */ + numChannels: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + sampleLength: 0, + /** @type {boolean} */ + freeForm: false +}; +/** + * The data of the cart chunk. + * @type {!Object} + */ +WaveFile.cart = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {string} */ + version: "", + /** @type {string} */ + title: "", + /** @type {string} */ + artist: "", + /** @type {string} */ + cutId: "", + /** @type {string} */ + clientId: "", + /** @type {string} */ + category: "", + /** @type {string} */ + classification: "", + /** @type {string} */ + outCue: "", + /** @type {string} */ + startDate: "", + /** @type {string} */ + startTime: "", + /** @type {string} */ + endDate: "", + /** @type {string} */ + endTime: "", + /** @type {string} */ + producerAppId: "", + /** @type {string} */ + producerAppVersion: "", + /** @type {string} */ + userDef: "", + /** @type {number} */ + levelReference: 0, + /** @type {string} */ + postTimer: "", + /** @type {string} */ + reserved: "", + /** @type {string} */ + url: "", + /** @type {string} */ + tagText: "" }; /** * The data of the 'iXML' chunk. @@ -1066,11 +1336,11 @@ WaveFile.bext = { */ WaveFile.iXML = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - value: '' + value: "" }; /** * The data of the "ds64" chunk. @@ -1078,40 +1348,40 @@ WaveFile.iXML = { * @type {!Object} */ WaveFile.ds64 = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - riffSizeHigh: 0, // DWORD - /** @type {number} */ - riffSizeLow: 0, // DWORD - /** @type {number} */ - dataSizeHigh: 0, // DWORD - /** @type {number} */ - dataSizeLow: 0, // DWORD - /** @type {number} */ - originationTime: 0, // DWORD - /** @type {number} */ - sampleCountHigh: 0, // DWORD - /** @type {number} */ - sampleCountLow: 0, // DWORD - /** @type {number} */ - //"tableLength": 0, // DWORD - /** @type {!Array} */ - //"table": [] + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + riffSizeHigh: 0, // DWORD + /** @type {number} */ + riffSizeLow: 0, // DWORD + /** @type {number} */ + dataSizeHigh: 0, // DWORD + /** @type {number} */ + dataSizeLow: 0, // DWORD + /** @type {number} */ + originationTime: 0, // DWORD + /** @type {number} */ + sampleCountHigh: 0, // DWORD + /** @type {number} */ + sampleCountLow: 0 // DWORD + /** @type {number} */ + //"tableLength": 0, // DWORD + /** @type {!Array} */ + //"table": [] }; /** * The data of the "data" chunk. * @type {!Object} */ WaveFile.data = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {!Uint8Array} */ - samples: new Uint8Array(0) + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {!Uint8Array} */ + samples: new Uint8Array(0) }; /** * The data of the "LIST" chunks. @@ -1130,12 +1400,12 @@ WaveFile.LIST = []; * @type {!Object} */ WaveFile.junk = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {!Array} */ - chunkData: [] + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {!Array} */ + chunkData: [] }; /** * The data of the '_PMX' chunk. @@ -1143,21 +1413,23 @@ WaveFile.junk = { */ WaveFile._PMX = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - value: '' + value: "" }; /** * The bit depth code according to the samples. * @type {string} */ -WaveFile.bitDepth = ''; +WaveFile.bitDepth = ""; ``` #### Cue points -Items in *cue.points* are objects like this: + +Items in _cue.points_ are objects like this: + ```javascript { /** @type {number} */ @@ -1176,7 +1448,9 @@ Items in *cue.points* are objects like this: ``` #### Sample loops -Items in *smpl.loops* are objects like this: + +Items in _smpl.loops_ are objects like this: + ```javascript { /** @type {string} */ @@ -1195,7 +1469,9 @@ Items in *smpl.loops* are objects like this: ``` #### LIST chunk + "LIST" chunk data is stored as follows: + ```javascript /** * An array of the "LIST" chunks present in the file. @@ -1204,7 +1480,8 @@ Items in *smpl.loops* are objects like this: WaveFile.LIST = []; ``` -Items in *WaveFile.LIST* are objects like this: +Items in _WaveFile.LIST_ are objects like this: + ```javascript { /** @type {string} */ @@ -1217,9 +1494,11 @@ Items in *WaveFile.LIST* are objects like this: subChunks: [] }; ``` + Where "subChunks" are the subChunks of the "LIST" chunk. A single file may have many "LIST" chunks as long as their formats ("INFO", "adtl", etc) are not the same. **wavefile** can read and write "LIST" chunks of format "INFO" and "adtl". For "LIST" chunks with the "INFO" format, "subChunks" will be an array of objects like this: + ```javascript { /** @type {string} */ @@ -1230,47 +1509,85 @@ For "LIST" chunks with the "INFO" format, "subChunks" will be an array of object value: '' } ``` + Where "chunkId" may be any RIFF tag: https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info ## Contributing to wavefile + **wavefile** welcomes all contributions from anyone willing to work in good faith with other contributors and the community. No contribution is too small and all contributions are valued. See [CONTRIBUTING.md](https://github.com/rochars/wavefile/blob/master/CONTRIBUTING.md) for details. ### Style guide + **wavefile** code should follow the Google JavaScript Style Guide: https://google.github.io/styleguide/jsguide.html ### Code of conduct + This project is bound by a Code of Conduct: The [Contributor Covenant, version 1.4](https://github.com/rochars/wavefile/blob/master/CODE_OF_CONDUCT.md), also available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +## Creator's note + +The creator of **wavefile**, @rochars, added this note to the README on the original project: + +### MOVING AWAY FROM GITHUB (2020-03-08) + +Microsoft, owner of GitHub, was one of the main backers of the current fascist regime in Brazil and also of the coup d'etat that led to the present situation of my country. + +It paid well: The brazilian government was required to run all its systems on open-source software. After the coup d'etat this changed, the goverment began purchasing Microsoft licenses and migrating all their systems to Windows. + +It is not just a case of business malpractice - there is a genocide going on in Brazil and many people, including myself, have lived under constant death threats for the past couple years bacause of our positions against the current fascist regime. Many have been murdered or incarcerated. Poverty and violence skyrocketed. + +**This software will keep being released in NPM as always - only the repository will be moved. Projects depending on this software will not be affected.** + +For Microsoft owners and collaborators: you have a lot of blood in your hands. I will not share my work with people of your kind. + +--- + ## References ### Papers -https://tech.ebu.ch/docs/tech/tech3285.pdf -https://tech.ebu.ch/docs/tech/tech3306-2009.pdf -http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html -https://www.loc.gov/preservation/digital/formats/fdd/fdd000356.shtml -http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf -https://sites.google.com/site/musicgapi/technical-documents/wav-file-format -http://www.neurophys.wisc.edu/auditory/riff-format.txt -https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info +#### General Wave/RIFF docs +- http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html +- http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf +- https://sites.google.com/site/musicgapi/technical-documents/wav-file-format +- http://www.neurophys.wisc.edu/auditory/riff-format.txt +- https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info + +#### Broadcast Wave Format +- Spec: https://tech.ebu.ch/docs/tech/tech3285.pdf +- Version 1: https://www.loc.gov/preservation/digital/formats/fdd/fdd000356.shtml +- MPEG Supplement: https://tech.ebu.ch/docs/tech/tech3285s1.pdf + +#### Broadcast Wave 64Bit (BW64) +- Current: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf +- Superseded: https://tech.ebu.ch/publications/tech3306 + +#### Cart Chunk +- Site: http://www.cartchunk.org/ +- Spec: http://www.aes.org/publications/standards/search.cfm?docID=41 ### Software -https://github.com/erikd/libsndfile -https://gist.github.com/hackNightly/3776503 -https://github.com/chirlu/sox/blob/master/src/wav.c + +- https://github.com/erikd/libsndfile +- https://gist.github.com/hackNightly/3776503 +- https://github.com/chirlu/sox/blob/master/src/wav.c +- https://github.com/kookster/nu_wav ### Other -https://developercertificate.org/ -https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -https://google.github.io/styleguide/jsguide.html + +- https://developercertificate.org/ +- https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +- https://google.github.io/styleguide/jsguide.html ## Legal + [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Frochars%2Fwavefile.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Frochars%2Fwavefile?ref=badge_large) ### LICENSE + Copyright (c) 2017-2019 Rafael da Silva Rocha. Permission is hereby granted, free of charge, to any person obtaining diff --git a/bin/wavefile.js b/bin/wavefile.js old mode 100644 new mode 100755 index f861927..9223b65 --- a/bin/wavefile.js +++ b/bin/wavefile.js @@ -67,6 +67,9 @@ const help = " Usage:\n" + " --list-cue Ex: wavefile input.wav --list-cue\n" + " Print all the cue points of the file.\n" + "\n" + + " --list-smpl Ex: wavefile input.wav --list-smpl\n" + + " Print all the sample chunks from the file.\n" + + "\n" + " --bits Ex: wavefile input.wav --bits\n" + " Print the bit depth of the file.\n" + "\n" + @@ -157,6 +160,9 @@ for (let command in commands) { // --list-cue } else if (command == '--list-cue') { console.log(wav.listCuePoints()); + // --list-smpl +} else if (command == '--list-smpl') { + console.log(wav.smpl); // --bits } else if (command == '--bits') { if (wav.fmt.validBitsPerSample) { diff --git a/dist/wavefile.js b/dist/wavefile.js index 0199892..76a6a84 100644 --- a/dist/wavefile.js +++ b/dist/wavefile.js @@ -1,84 +1,104 @@ -try{if(!Uint8Array.prototype.slice)Object.defineProperty(Uint8Array.prototype,"slice",{value:function(begin,end){return new Uint8Array(Array.prototype.slice.call(this,begin,end))}})}catch(err){}var ka="function"==typeof Object.create?Object.create:function(n){function m(){}m.prototype=n;return new m},D; -if("function"==typeof Object.setPrototypeOf)D=Object.setPrototypeOf;else{var K;a:{var la={D:!0},N={};try{N.__proto__=la;K=N.D;break a}catch(n){}K=!1}D=K?function(n,m){n.__proto__=m;if(n.__proto__!==m)throw new TypeError(n+" is not extensible");return n}:null}var O=D; -function P(n,m){n.prototype=ka(m.prototype);n.prototype.constructor=n;if(O)O(n,m);else for(var l in m)if("prototype"!=l)if(Object.defineProperties){var p=Object.getOwnPropertyDescriptor(m,l);p&&Object.defineProperty(n,l,p)}else n[l]=m[l]}var ma="function"==typeof Object.defineProperties?Object.defineProperty:function(n,m,l){n!=Array.prototype&&n!=Object.prototype&&(n[m]=l.value)},na="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this; -function Q(n,m){if(m){for(var l=na,p=n.split("."),y=0;yv&&(v=Math.max(v+y,0));vp||56319m||57343c;c++)b["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charCodeAt(c)]=c;c=.75*a.length;"="===a[a.length-1]&&(c--,"="===a[a.length-2]&&c--);c=new Uint8Array(c);for(var d= -0,e=0;d>4;c[e++]=(g&15)<<4|k>>2;c[e++]=(k&3)<<6|r&63}return c}function sa(a,b){return a=0parseInt(a,10)||"53">4;z.push(ba(aa<<4^Z,r));z.push(ba(aa,r))}e=z;d.set(e,f);f+=e.length;e=[]}e.push(a[g])}return d}function xa(a,b){var c=a[0];S(c,b);var d=[];d.push(c&255);d.push(c>>8&255);d.push(b.index);d.push(0);c=3;for(var e=a.length;c>3;c>e&&(d|=4,c-=e,f+=e);e>>=1;c>e&&(d|=2,c-=e,f+=e);e>>=1;c>e&&(d|=1,f+=e);c=d;b.i=c&8?b.i-f:b.i+f;-32768>b.i?b.i=-32768:32767b.index?b.index=0:88>1);a&1&&(c+=b.step>>2);c+=b.step>>3;a&8&&(c=-c);b.i+=c;32767b.i&&(b.i=-32767);b.index+=ca[a];0>b.index?b.index=0:88>8&128;g||(f*=-1);32635>8&127];f=k<<4|f>>k+3&15}else f>>=4;b[e]=f^g^85}return b}function Ca(a){for(var b=new Int16Array(a.length),c=0,d=a.length;c>4)+4;f=4!=k?1<>8&128;0!=g&&(f=-f);f+=132;32635>7&255];b[e]=~(g|k<<4|f>>k+3&15)}return b}function Fa(a){for(var b=new Int16Array(a.length),c=0,d=a.length;c>4&7;g=Ga[g]+((f&15)<f)b[c]=f,c++;else{var g=0,k=0;2047>=f?(g=1,k=192):65535>=f?(g=2,k=224):1114111>=f&&(g=3,k=240,d++);b[c]=(f>>6*g)+k;for(c++;0>6*(g-1)&63,c++,g--}d++}return c}function U(a){var b=Math.floor(a);a-=b;return.5>a?b:.5=k)b+=String.fromCharCode(k);else{var r= -0;194<=k&&223>=k?r=1:224<=k&&239>=k?(r=2,224===a[d]&&(e=160),237===a[d]&&(f=159)):240<=k&&244>=k?(r=3,240===a[d]&&(e=144),244===a[d]&&(f=143)):g=!0;k&=(1<<8-r-1)-1;for(var z=0;zf)g=!0;k=k<<6|a[d]&63;d++}g?b+=String.fromCharCode(65533):65535>=k?b+=String.fromCharCode(k):(k-=65536,b+=String.fromCharCode((k>>10&1023)+55296,(k&1023)+56320))}}return b}function x(a){var b=[];T(a,b);return b}function L(a,b,c,d){d=void 0===d?0:d;b=b||{};for(var e=ea(b.h,b.R,b.O),f=Math.ceil(b.h/8), -g=0,k=d,r=a.length;gb){b=new g(d.LPForder||ia[d.LPFType],c,b/2);c=0;for(d=e.length;cthis.max?this.max:athis.max&&(a-=2*this.max+2);return a};H.prototype.ga=function(a,b,c){Math.abs(b)>this.f-2*this.g&&(b=0>b?-Infinity:Infinity);var d=0>((b=+b)||1/b)?1:0>b?1:0;b=Math.abs(b);var e=Math.min(Math.floor(Math.log(b)/Math.LN2),1023),f=U(b/Math.pow(2,e)*Math.pow(2,this.c));b!== -b?(f=Math.pow(2,this.c-1),e=(1<=Math.pow(2,1-this.a)?(2<=f/Math.pow(2,this.c)&&(e+=1,f=1),e>this.a?(e=(1<=b;)a[f]=parseInt(e.substring(0,8),2),e=e.substring(8),f--,d++;return d};B.prototype.va=function(a){this.c=0;this.container=this.u(a,4);if(-1===this.Z.indexOf(this.container))throw Error("Not a supported format."); -this.a.o="RIFX"===this.container;this.chunkSize=this.b(a);this.format=this.u(a,4);this.Y={chunkId:this.container,chunkSize:this.chunkSize,format:this.format,subChunks:this.V(a)}};B.prototype.s=function(a,b){b=void 0===b?!1:b;for(var c=this.Y.subChunks,d=[],e=0;ethis.data.samples.length)throw Error("Range error");return M(this.data.samples.slice(a,a+this.f.h/8),this.f)};t.prototype.setSample=function(a,b){a*=this.f.h/ -8;if(a+this.f.h/8>this.data.samples.length)throw Error("Range error");L([b],this.f,this.data.samples,void 0===a?0:a)};t.prototype.getiXML=function(){return this.iXML.value};t.prototype.setiXML=function(a){if("string"!==typeof a)throw new TypeError("iXML value must be a string.");this.iXML.value=a;this.iXML.chunkId="iXML"};t.prototype.get_PMX=function(){return this._PMX.value};t.prototype.set_PMX=function(a){if("string"!==typeof a)throw new TypeError("_PMX value must be a string.");this._PMX.value= -a;this._PMX.chunkId="_PMX"};t.prototype.W=function(a,b,c,d,e){e.container||(e.container="RIFF");this.container=e.container;this.bitDepth=c;var f=[];if(0parseInt(this.bitDepth,10)))throw Error("Invalid bit depth.");};t.prototype.$=function(){this.f={h:(parseInt(this.bitDepth,10)-1|7)+1,R:"32f"==this.bitDepth||"64"==this.bitDepth,O:"8"!=this.bitDepth,o:"RIFX"==this.container};-1<["4","8a","8m"].indexOf(this.bitDepth)&&(this.f.h=8,this.f.O=!1)};t.prototype.aa=function(){this.wa(); -var a=this.fmt.numChannels;if(1>a||65535a||4294967295c.length)for(var d=0,e=4-c.length;db.dwSampleOffset&&!c?(this.B(b,d+1),this.B(a[d],d+2),c=!0):this.B(a[d],c?d+2:d+1);c||this.B(b,this.cue.points.length+1)};w.prototype.I=function(){for(var a=0,b=this.LIST.length;aa||4294967295>2],b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(a[c]&3)<<4|a[c+1]>>4],b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(a[c+1]&15)<<2|a[c+2]>>6],b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[a[c+2]&63];2===a.length%3?b=b.substring(0,b.length-1)+"=":1===a.length%3&&(b=b.substring(0,b.length-2)+"==");return b};m.prototype.toDataURI= -function(){return"data:audio/wav;base64,"+this.toBase64()};m.prototype.fromDataURI=function(a){this.fromBase64(a.replace("data:audio/wav;base64,",""))};n.WaveFile=m;Object.defineProperty(n,"__esModule",{value:!0})}"object"===typeof exports&&"undefined"!==typeof module?W(exports):"function"===typeof define&&define.amd?define(["exports"],W):(V=V||self,W(V.wavefile={})); +try{if(!Uint8Array.prototype.slice)Object.defineProperty(Uint8Array.prototype,"slice",{value:function(begin,end){return new Uint8Array(Array.prototype.slice.call(this,begin,end))}})}catch(err){}function ea(h){var l=0;return function(){return lv&&(v=Math.max(v+u,0));vu||56319l||57343>>0)+"_",u=0;return l}); +L("Symbol.iterator",function(h){if(h)return h;h=Symbol("Symbol.iterator");for(var l="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),m=0;mparseInt(a,10)||"53">3;c>e&&(d|=4,c-=e,f+=e);e>>=1;c>e&&(d|=2,c-=e,f+=e);e>>=1;c>e&&(d|=1,f+=e);c=d;b.u=c&8?b.u-f:b.u+f;-32768>b.u?b.u=-32768:32767b.index?b.index=0:88>1);a&1&&(c+=b.step>>2);c+=b.step>>3;a&8&&(c=-c);b.u+=c;32767b.u&&(b.u=-32767);b.index+=la[a];0>b.index?b.index=0:88f)b[c]=f,c++;else{var k=0,p=0;2047>=f?(k=1,p=192):65535>=f?(k=2,p=224):1114111>=f&&(k=3,p=240,d++);b[c]=(f>>6*k)+p;for(c++;0>6*(k-1)&63,c++,k--}d++}return c}function da(a){var b=Math.floor(a);a-=b;return.5>a?b:.5=p)b+=String.fromCharCode(p);else{var n= +0;194<=p&&223>=p?n=1:224<=p&&239>=p?(n=2,224===a[d]&&(e=160),237===a[d]&&(f=159)):240<=p&&244>=p?(n=3,240===a[d]&&(e=144),244===a[d]&&(f=143)):k=!0;p&=(1<<8-n-1)-1;for(var r=0;rf)k=!0;p=p<<6|a[d]&63;d++}k?b+=String.fromCharCode(65533):65535>=p?b+=String.fromCharCode(p):(p-=65536,b+=String.fromCharCode((p>>10&1023)+55296,(p&1023)+56320))}}return b}function B(a){var b=[];ca(a,b);return b}function S(a,b,c,d){d=void 0===d?0:d;b=b||{};for(var e=oa(b.s,b.V,b.T),f=Math.ceil(b.s/8), +k=0,p=d,n=a.length;kb){b=new k(d.LPForder||ra[d.LPFType],c,b/2);c=0;for(d=e.length;cthis.max?this.max:athis.max&&(a-=2*this.max+2);return a};P.prototype.ja=function(a,b,c){Math.abs(b)>this.j-2*this.m&&(b=0>b?-Infinity:Infinity);var d=0>((b=+b)||1/b)?1:0>b?1:0;b=Math.abs(b);var e=Math.min(Math.floor(Math.log(b)/Math.LN2),1023),f=da(b/Math.pow(2,e)*Math.pow(2,this.i));b!== +b?(f=Math.pow(2,this.i-1),e=(1<=Math.pow(2,1-this.g)?(2<=f/Math.pow(2,this.i)&&(e+=1,f=1),e>this.g?(e=(1<=b;)a[f]=parseInt(e.substring(0,8),2),e=e.substring(8),f--,d++;return d};F.prototype.Ra=function(a){this.i=0;this.container=this.j(a,4);if(-1===this.ba.indexOf(this.container))throw Error("Not a supported format."); +this.g.D="RIFX"===this.container;this.chunkSize=this.h(a);this.format=this.j(a,4);this.aa={chunkId:this.container,chunkSize:this.chunkSize,format:this.format,subChunks:this.Y(a)}};F.prototype.A=function(a,b){b=void 0===b?!1:b;for(var c=this.aa.subChunks,d=[],e=0;ec;c++)this.cart.postTimer.push({usage:this.j(a,4),value:this.h(a)});this.cart.reserved=this.j(a,276);this.cart.url=this.j(a,1024);this.cart.tagText=this.j(a,b.chunkSize-2048)}};y.prototype.Ha=function(a){var b=this.A("iXML");b&&(this.i=b.chunkData.start,this.iXML.chunkId=b.chunkId,this.iXML.chunkSize=b.chunkSize,this.iXML.value=M(a,this.i,this.i+ +this.iXML.chunkSize))};y.prototype.va=function(a){var b=this.A("ds64");if(b)this.i=b.chunkData.start,this.ds64.chunkId=b.chunkId,this.ds64.chunkSize=b.chunkSize,this.ds64.riffSizeHigh=this.h(a),this.ds64.riffSizeLow=this.h(a),this.ds64.dataSizeHigh=this.h(a),this.ds64.dataSizeLow=this.h(a),this.ds64.originationTime=this.h(a),this.ds64.sampleCountHigh=this.h(a),this.ds64.sampleCountLow=this.h(a);else if("RF64"==this.container)throw Error('Could not find the "ds64" chunk');};y.prototype.Aa=function(a){var b= +this.A("LIST",!0);if(null!==b)for(var c=0;cb;b++){var c="",d=4294967295;b>=1,d|=b&c,c>>=8;b=10+d;a[5]&2&&(b+=10)}return b};E.prototype.G=function(a){this.H(a);this.numChannels= +"mono"==this.channelMode?1:2;this.i=this.l[this.version][this.layer];this.frameSize=this.C();this.sampleLength=this.K(a)};E.prototype.K=function(a){return Math.floor((a.length-this.h)/this.frameSize)*this.i};E.prototype.C=function(){return this.F(this.i,this.layer,this.bitRate,this.sampleRate,this.padding)};E.prototype.F=function(a,b,c,d,e){return 1E3*c/8*a/d+(e?1:0)*(1==b?4:1)|0};E.prototype.H=function(a){for(var b=[],c=0;4>c;c++)b[c]=this.J(a);if(255!==b[0]||224!==(b[1]&224))throw Error("Invalid frame header: [255, 224] != ["+ +b[0]+", "+b[1]+"]");this.version=this.v[b[1]>>3&3];this.layer=this.o[b[1]>>1&3];this.errorProtection=!(b[1]&1);this.bitRate=this.j[this.version][this.layer][b[2]>>4&15];this.sampleRate=this.A[this.version][b[2]>>2&3];this.padding=b[2]&2?!0:!1;this.privateBit=b[2]&1?!0:!1;this.channelMode=this.m[b[3]>>6&3];this.modeExtension=b[3]>>6&3;this.copyright=b[3]&8?!0:!1;this.original=b[3]&4?!0:!1;this.emphasis=b[3]&3};E.prototype.J=function(a){a=a[this.g];this.g+=1;return a};X(w,x);w.prototype.fromScratch= +function(a,b,c,d,e){e=e||{};this.M();this.Z(a,b,c,d,e)};w.prototype.fromMpeg=function(a,b){b=void 0===b?null:b;this.M();null==b&&(b=new E(a));var c=this.yb(b);this.container="RIFF";this.chunkSize=694+c.length+a.length;this.format="WAVE";this.bitDepth="65535";this.fmt.chunkId="fmt ";this.fmt.chunkSize=40;this.fmt.audioFormat=80;this.fmt.numChannels=b.numChannels;this.fmt.sampleRate=b.sampleRate;this.fmt.byteRate=b.bitRate/8*1E3;this.fmt.blockAlign=b.frameSize;this.fmt.bitsPerSample=65535;this.fmt.cbSize= +22;this.fmt.headLayer=Math.pow(2,b.layer-1);this.fmt.headBitRate=1E3*b.bitRate;this.fmt.headMode=this.na(b);this.fmt.headModeExt=this.ma(b);this.fmt.headEmphasis=b.emphasis+1;this.fmt.headFlags=this.zb(b);this.fmt.ptsLow=0;this.fmt.ptsHigh=0;this.mext.chunkId="mext";this.mext.chunkSize=12;this.mext.soundInformation=this.oa(b);this.mext.frameSize=b.frameSize;this.mext.ancillaryDataLength=0;this.mext.ancillaryDataDef=0;this.mext.reserved="";this.bext.chunkId="bext";this.bext.chunkSize=602+c.length; +this.bext.timeReference=[0,0];this.bext.version=1;this.bext.codingHistory=c;this.fact.chunkId="fact";this.fact.chunkSize=4;this.fact.dwSampleLength=b.sampleLength;this.data.chunkId="data";this.data.samples=a;this.data.chunkSize=this.data.samples.length};w.prototype.oa=function(a){var b=0;a.hb&&(b+=1);a.padding||(b+=2);44100!=a.sampleRate&&22050!=a.sampleRate||a.padding||(b+=4);a.freeForm&&(b+=8);return b};w.prototype.na=function(a){return{stereo:1,"joint-stereo":2,"dual-mono":4,mono:8}[a.channelMode]}; +w.prototype.ma=function(a){return"joint-stereo"==a.channelMode?Math.pow(2,a.modeExtension):0};w.prototype.yb=function(a){return"A=MPEG"+a.version+"L"+a.layer+",F="+a.sampleRate+",B="+a.bitRate+",M="+a.channelMode+",T=wavefile\r\n\x00\x00"};w.prototype.zb=function(a){var b=0;a.privateBit&&(b+=1);a.copyright&&(b+=2);a.original&&(b+=4);a.errorProtection&&(b+=8);0this.data.samples.length)throw Error("Range error");return Q(this.data.samples.slice(a,a+this.l.s/8),this.l)};w.prototype.setSample=function(a,b){a*=this.l.s/8;if(a+this.l.s/8>this.data.samples.length)throw Error("Range error");S([b],this.l,this.data.samples,void 0===a?0:a)};w.prototype.getiXML=function(){return this.iXML.value};w.prototype.setiXML=function(a){if("string"!==typeof a)throw new TypeError("iXML value must be a string.");this.iXML.value=a;this.iXML.chunkId= +"iXML"};w.prototype.get_PMX=function(){return this._PMX.value};w.prototype.set_PMX=function(a){if("string"!==typeof a)throw new TypeError("_PMX value must be a string.");this._PMX.value=a;this._PMX.chunkId="_PMX"};w.prototype.Z=function(a,b,c,d,e){e.container||(e.container="RIFF");this.container=e.container;this.bitDepth=c;var f=[];if(0parseInt(this.bitDepth,10)))throw Error("Invalid bit depth.");};w.prototype.ca= +function(){this.l={s:(parseInt(this.bitDepth,10)-1|7)+1,V:"32f"==this.bitDepth||"64"==this.bitDepth,T:"8"!=this.bitDepth,D:"RIFX"==this.container};-1<["4","8a","8m"].indexOf(this.bitDepth)&&(this.l.s=8,this.l.T=!1)};w.prototype.da=function(){this.Ta();var a=this.fmt.numChannels;if(1>a||65535a||4294967295c.length)for(var d=0,e=4-c.length;d +b.dwSampleOffset&&!c?(this.F(b,d+1),this.F(a[d],d+2),c=!0):this.F(a[d],c?d+2:d+1);c||this.F(b,this.cue.points.length+1)};A.prototype.L=function(){for(var a=0,b=this.LIST.length;a>8&255);K.push(C.index);K.push(0);D=3;for(var T=k.length;D>4;C.push(ma(U<<4^T,G));C.push(ma(U,G))}n=C;p.set(n,r);r+=n.length;n=[]}n.push(e[z])}b.call(this,c,d,"16",p,{container:this.C()});"16"!=a&&this.toBitDepth(a)};m.prototype.toALaw= +function(){this.K();var a=new Int16Array(R(this.data.samples.length,2));N(this.data.samples,this.l,a,0,this.data.samples.length);for(var b=this.B,c=this.fmt.numChannels,d=this.fmt.sampleRate,e=new Uint8Array(a.length),f=0,k=a.length;f>8&128;r||(n*=-1);32635>8&127];n=z<<4|n>>z+3&15}else n>>=4;e[p]=n^r^85}b.call(this,c,d,"8a",e,{container:this.C()})};m.prototype.fromALaw=function(a){a=void 0===a?"16":a;for(var b= +this.B,c=this.fmt.numChannels,d=this.fmt.sampleRate,e=this.data.samples,f=new Int16Array(e.length),k=0,p=e.length;k>4)+4;r=4!=I?1<>8&128;0!=r&&(n=-n);n+=132;32635>7&255];e[p]=~(r|z<<4|n>>z+3&15)}b.call(this,c,d,"8m",e,{container:this.C()})};m.prototype.fromMuLaw=function(a){a=void 0===a?"16":a;for(var b=this.B,c=this.fmt.numChannels,d=this.fmt.sampleRate,e=this.data.samples,f=new Int16Array(e.length),k=0,p=e.length;k>4&7;z=Ka[z]+((r&15)<a||4294967295d;d++)c["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charCodeAt(d)]=d;d=.75*a.length;"="===a[a.length-1]&&(d--,"="===a[a.length-2]&&d--);d=new Uint8Array(d);for(var e=0,f=0;e>4;d[f++]=(p&15)<<4|n>>2;d[f++]=(n&3)<<6|r&63}b.call(this,d)};l.prototype.toBase64=function(){for(var a=this.toBuffer(),b="",c=0;c>2],b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(a[c]&3)<<4|a[c+1]>>4],b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(a[c+1]&15)<<2|a[c+2]>>6],b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[a[c+ +2]&63];2===a.length%3?b=b.substring(0,b.length-1)+"=":1===a.length%3&&(b=b.substring(0,b.length-2)+"==");return b};l.prototype.toDataURI=function(){return"data:audio/wav;base64,"+this.toBase64()};l.prototype.fromDataURI=function(a){this.fromBase64(a.replace("data:audio/wav;base64,",""))};h.WaveFile=l;Object.defineProperty(h,"__esModule",{value:!0})} +"object"===typeof exports&&"undefined"!==typeof module?Z(exports):"function"===typeof define&&define.amd?define(["exports"],Z):(Y=Y||self,Z(Y.wavefile={})) diff --git a/dist/wrapper.mjs b/dist/wrapper.mjs new file mode 100644 index 0000000..4facce7 --- /dev/null +++ b/dist/wrapper.mjs @@ -0,0 +1,3 @@ +import cjsModule from './wavefile.js'; + +export const WaveFile = cjsModule.WaveFile; diff --git a/docs/index.html b/docs/index.html index b412cba..493bb2a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -27,7 +27,7 @@
@@ -57,13 +57,12 @@

-

wavefile

+

prx-wavefile

Copyright (c) 2017-2019 Rafael da Silva Rocha.
https://github.com/rochars/wavefile

NPM version Docs Tests
Codecov Unix Build Windows Build Scrutinizer CII Best Practices

-

Notice

-

My country, Brazil, is under a fascist government that is hunting and killing its opponents. I've been threatened too.

+

prx-wavefile

Create, read and write wav files according to the specs.

  • MIT licensed
  • @@ -73,7 +72,13 @@

    Notice

  • Handle files up to 2GB
  • Zero dependencies
-

With wavefile you can:

+

prx-wavefile is a fork of the excellent wavefile project created by @rochars.

+

This fork also supports these additional features:

+ +

With prx-wavefile you can:

And more.

Install

@@ -95,15 +102,15 @@

Install

Use

Node

-
const wavefile = require('wavefile');
+
const wavefile = require("prx-wavefile");
 let wav = new wavefile.WaveFile();
 

or

-
const WaveFile = require('wavefile').WaveFile;
+
const WaveFile = require("prx-wavefile").WaveFile;
 let wav = new WaveFile();
 

or

-
import { WaveFile } from 'wavefile';
+
import { WaveFile } from "prx-wavefile";
 let wav = new WaveFile();
 

Browser

@@ -113,11 +120,11 @@

Browser

var wav = new wavefile.WaveFile(); </script>
-

Or load it from the jsDelivr CDN:

-
<script src="https://cdn.jsdelivr.net/npm/wavefile"></script>
+

Or load it from the jsDelivr CDN:

+
<script src="https://cdn.jsdelivr.net/npm/prx-wavefile"></script>
 
-

Or load it from unpkg:

-
<script src="https://unpkg.com/wavefile"></script>
+

Or load it from unpkg:

+
<script src="https://unpkg.com/prx-wavefile"></script>
 

Browser compatibility

IE10+. Should work in all modern browsers.

@@ -128,7 +135,7 @@

Command line use

wavefile --help
 

Node.js Example

-
const WaveFile = require('wavefile').WaveFile;
+
const WaveFile = require("prx-wavefile").WaveFile;
 
 // Load a wav file buffer as a WaveFile object
 let wav = new WaveFile(buffer);
@@ -163,6 +170,8 @@ 

Table of Contents

  • Change the bit depth
  • Change the sample rate
  • Add BWF metadata
  • +
  • Create BWF from MPEG
  • +
  • Cart Chunk
  • RF64
  • XML Chunks
  • The samples
  • @@ -192,16 +201,16 @@

    Mono:

    let wav = new WaveFile();
     
     // Create a mono wave file, 44.1 kHz, 32-bit and 4 samples
    -wav.fromScratch(1, 44100, '32', [0, -2147483, 2147483, 4]);
    +wav.fromScratch(1, 44100, "32", [0, -2147483, 2147483, 4]);
     fs.writeFileSync(path, wav.toBuffer());
     

    Stereo:

    Samples can be informed interleaved or de-interleaved. If they are de-interleaved, WaveFile will interleave them. In this example they are de-interleaved.

    // Stereo, 48 kHz, 8-bit, de-interleaved samples
     // WaveFile interleave the samples automatically
    -wav.fromScratch(2, 48000, '8', [
    -    [0, 2, 4, 3],
    -    [0, 1, 4, 3]
    +wav.fromScratch(2, 48000, "8", [
    +  [0, 2, 4, 3],
    +  [0, 1, 4, 3]
     ]);
     fs.writeFileSync(path, wav.toBuffer());
     
    @@ -219,7 +228,7 @@

    Stereo:

    A word on bit depth

    Resolutions other than 4-bit, 8-bit, 16-bit, 24-bit, 32-bit (integer), 32-bit (fp) and 64-bit (fp) are implemented as WAVE_FORMAT_EXTENSIBLE and may not be supported by some players.

    Read wave files

    -
    const WaveFile = require('wavefile').WaveFile;
    +
    const WaveFile = require("prx-wavefile").WaveFile;
     wav = new WaveFile();
     // Read a wav file from a buffer
     wav.fromBuffer(buffer);
    @@ -243,11 +252,11 @@ 

    Add RIFF tags to files

    Add cue points to files

    You can create cue points using the WaveFile.setCuePoint() method. The method takes a object with the cue point data and creates a cue point in the corresponding position of the file. The only required attribute of the object is position, a number representing the position of the point in milliseconds:

    // to create a cue point
    -wav.setCuePoint({position: 1500});
    +wav.setCuePoint({ position: 1500 });
     

    You can also create cue points with labels by defining a label attribute:

    // to create a cue point with a label
    -wav.setCuePoint({position: 1500, label: 'some label'});
    +wav.setCuePoint({ position: 1500, label: "some label" });
     

    To delete a cue point use WaveFile.deleteCuePoint() informing the index of the point. Points are ordered according to their position. The first point is indexed as 1.

    wav.deleteCuePoint(1);
    @@ -260,11 +269,11 @@ 

    Add cue points to files

    [
       {
         position: 500, // the position in milliseconds
    -    label: 'cue marker 1',
    +    label: "cue marker 1",
         end: 1500, // the end position in milliseconds
         dwName: 1,
         dwPosition: 0,
    -    fccChunk: 'data',
    +    fccChunk: "data",
         dwChunkStart: 0,
         dwBlockStart: 0,
         dwSampleOffset: 22050, // the position as a sample offset
    @@ -273,8 +282,8 @@ 

    Add cue points to files

    dwCountry: 0, dwLanguage: 0, dwDialect: 0, - dwCodePage: 0, - }, + dwCodePage: 0 + } //... ];
    @@ -282,7 +291,7 @@

    Create regions in files

    You can create regions using the WaveFile.setCuePoint() method. Regions are cue points with extra data.

    If you define a not null end attribute in the object describing the cue point, the point will be created as a region. The end attribute should be the end of the region, in milliseconds, counting from the start of the file, and always greater than the position of the point:

    // to create a region with a label:
    -wav.setCuePoint({position: 1500, end: 2500, label: 'some label'});
    +wav.setCuePoint({ position: 1500, end: 2500, label: "some label" });
     

    You can also define the following optional properties when creating a region:

      @@ -293,8 +302,8 @@

      Create regions in files

    • dwCodePage

    RIFX

    -

    wavefile can handle existing RIFX files and create RIFX files from scratch. Files created from scratch will default to RIFF; to create a file as RIFX you must define the container:

    -
    wav.fromScratch(1, 48000, '16', [0, 1, -3278, 327], {"container": "RIFX"});
    +

    WaveFile can handle existing RIFX files and create RIFX files from scratch. Files created from scratch will default to RIFF; to create a file as RIFX you must define the container:

    +
    wav.fromScratch(1, 48000, "16", [0, 1, -3278, 327], { container: "RIFX" });
     

    RIFX to RIFF and RIFF to RIFX:

    // Turn a RIFF file to a RIFX file
    @@ -308,7 +317,7 @@ 

    IMA-ADPCM

    // Encode a 16-bit wave file as 4-bit IMA-ADPCM:
     wav.toIMAADPCM();
     
    -

    IMA-ADPCM files compressed with wavefile will have a block align of 256 bytes.

    +

    IMA-ADPCM files compressed with WaveFile will have a block align of 256 bytes.

    If the audio is not 16-bit it will be converted to 16-bit before compressing. Compressing audio with sample rate different from 8000 Hz or more than one channel is not supported and will throw errors.

    To decode 4-bit IMA-ADPCM as 16-bit linear PCM:

    // Decode 4-bit IMA-ADPCM as 16-bit:
    @@ -373,7 +382,7 @@ 

    Change the sample rate

    To use another method:

    // Change the sample rate to 44.1kHz using sinc
    -wav.toSampleRate(44100, {method: "sinc"});
    +wav.toSampleRate(44100, { method: "sinc" });
     

    Resampling methods

      @@ -384,17 +393,17 @@

      Resampling methods

    You can turn the LPF on and off for any resampling method:

    // Will use 'sinc' method with no LPF
    -wav.toSampleRate(44100, {method: "sinc", LPF: false});
    +wav.toSampleRate(44100, { method: "sinc", LPF: false });
     
     // Will use 'linear' method with LPF
    -wav.toSampleRate(44100, {method: "linear", LPF: true});
    +wav.toSampleRate(44100, { method: "linear", LPF: true });
     

    The default LPF is a IIR LPF. You may define what type of LPF will be used by changing the LPFType attribute on the toSampleRate() param. You can use IIR or FIR:

    // Will use 'linear' method with a FIR LPF
    -wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'});
    +wav.toSampleRate(44100, { method: "linear", LPF: true, LPFType: "FIR" });
     
     // Will use 'linear' method with a IIR LPF, the default
    -wav.toSampleRate(44100, {method: "linear", LPF: true});
    +wav.toSampleRate(44100, { method: "linear", LPF: true });
     

    Changing the sample rate of ADPCM, mu-Law or A-Law

    You need to convert compressed files to standard PCM before resampling:

    @@ -402,7 +411,7 @@

    Changing the sample rate of ADPCM, mu-Law or A-Law

    // convert the file to PCM
     wav.fromMuLaw();
     // resample
    -wav.toSampleRate(44100, {method: "sinc"});
    +wav.toSampleRate(44100, { method: "sinc" });
     // back to mu-Law
     wav.toMuLaw();
     
    @@ -417,11 +426,55 @@

    Add BWF metadata

    // Write the new BWF file fs.writeFileSync("32bit-file-with-bext.wav", wav.toBuffer());
    -

    By default wavefile will not insert a "bext" chunk in new files or in files that do not already have a "bext" chunk unless a property of WaveFile.bext is changed from it's default value. See below the full list of properties in WaveFile.bext.

    +

    By default WaveFile will not insert a "bext" chunk in new files or in files that do not already have a "bext" chunk unless a property of WaveFile.bext is changed from it's default value. See below the full list of properties in WaveFile.bext.

    +

    Create From MPEG

    +

    Make a wav file from an mpeg audio file, with optional metadata, using WaveFile.fromMpeg(). +There is now an MPEG reader in prx-wavefile that is used to +read metadata from the MPEG and automatically set that in "fact", "fmt", "bext", and "mext" chunks.

    +

    The MPEG specific "mext" chunk is specified below in WaveFile.mext.

    +

    The optional info is specified below in WaveFile.mpegInfo.

    +
    // You can create a wav from just an MPEG audio buffer/stream.
    +let wav = new WaveFile();
    +wav.fromMpeg(fs.readFileSync("test.mp2"));
    +
    +// You can also pass in the mpeg metadata info if you prefer
    +// This is specified by WaveFile.mpegInfo
    +let info = {
    +  version: 1,
    +  layer: 2,
    +  sampleRate: 44100,
    +  bitRate: 128,
    +  channelMode: "stereo",
    +  padding: 1,
    +  modeExtension: 0,
    +  emphasis: 0,
    +  privateBit: 1,
    +  copyright: true,
    +  original: true,
    +  errorProtection: true,
    +  numChannels: 2,
    +  frameSize: 768,
    +  sampleLength: 269568,
    +  freeForm: true
    +};
    +let wav2 = new WaveFile();
    +wav2.fromMpeg(fs.readFileSync("test.mp2"), info);
    +
    +

    Add Cart Chunk

    +

    By default WaveFile will not insert a "cart" chunk in new files or in files that do not already have a "cart" chunk unless a property of WaveFile.cart is changed from it's default value. See below the full list of properties in WaveFile.cart.

    +
    let wav = new WaveFile();
    +wav.fromMpeg(fs.readFileSync("test.mp2"));
    +
    +// Use the wav.cart to set the values
    +wav.cart.chunkId = "cart";
    +wav.cart.cutId = "30000";
    +wav.cart.title = "Title";
    +wav.cart.artist = "Artist";
    +

    RF64

    -

    wavefile have limited support of RF64 files. It possible to read (at least some) RF64 files, but changing the bit depth or applying compression to the samples will result in a RIFF file.

    +

    WaveFile have limited support of RF64 files. It possible to read (at least some) RF64 files, but changing the bit depth or applying compression to the samples will result in a RIFF file.

    XML Chunks

    -

    wavefile support reading and writing iXML and _PMX chunks.

    +

    Wavefile support reading and writing iXML and _PMX chunks.

    To get the value of iXML or _PMX chunks:

    /** @type {string} */
     let iXMLValue = wav.getiXML();
    @@ -561,6 +614,14 @@ 

    The WaveFile methods

    */ WaveFile.fromScratch(numChannels, sampleRate, bitDepth, samples, options) {} +/** + * Set up the WaveFileCreator object from an mpeg buffer and metadata info. + * @param {!Uint8Array} mpegBuffer The buffer. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ +WaveFile.fromMpeg(mpegBuffer, info=null) {}; + /** * Set up the WaveFileParser object from a byte buffer. * @param {!Uint8Array} wavBuffer The buffer. @@ -724,7 +785,7 @@

    The WaveFile methods

    * pointData.dwLanguage * pointData.dwDialect * pointData.dwCodePage - * + * * # This is what a complete pointData object look like: * { * position: number, @@ -836,11 +897,11 @@

    WaveFile.listCuePoints()

    [
       {
         position: 500, // the position in milliseconds
    -    label: 'cue marker 1',
    +    label: "cue marker 1",
         end: 1500, // the end position in milliseconds
         dwName: 1,
         dwPosition: 0,
    -    fccChunk: 'data',
    +    fccChunk: "data",
         dwChunkStart: 0,
         dwBlockStart: 0,
         dwSampleOffset: 22050, // the position as a sample offset
    @@ -850,9 +911,9 @@ 

    WaveFile.listCuePoints()

    dwLanguage: 0, dwDialect: 0, dwCodePage: 0 - }, + } // ... -] +];

    The list order reflects the order of the points in the file.

    The WaveFile properties

    @@ -861,7 +922,7 @@

    The WaveFile properties

    * "RIFF", "RIFX" and "RF64" are supported. * @type {string} */ -WaveFile.container = ''; +WaveFile.container = ""; /** * @type {number} */ @@ -871,138 +932,246 @@

    The WaveFile properties

    * Always 'WAVE'. * @type {string} */ -WaveFile.format = ''; +WaveFile.format = ""; /** * The data of the "fmt" chunk. * @type {!Object<string, *>} */ WaveFile.fmt = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - audioFormat: 0, - /** @type {number} */ - numChannels: 0, - /** @type {number} */ - sampleRate: 0, - /** @type {number} */ - byteRate: 0, - /** @type {number} */ - blockAlign: 0, - /** @type {number} */ - bitsPerSample: 0, - /** @type {number} */ - cbSize: 0, - /** @type {number} */ - validBitsPerSample: 0, - /** @type {number} */ - dwChannelMask: 0, - /** - * 4 32-bit values representing a 128-bit ID - * @type {!Array<number>} - */ - subformat: [] + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + audioFormat: 0, + /** @type {number} */ + numChannels: 0, + /** @type {number} */ + sampleRate: 0, + /** @type {number} */ + byteRate: 0, + /** @type {number} */ + blockAlign: 0, + /** @type {number} */ + bitsPerSample: 0, + /** @type {number} */ + cbSize: 0, + /** @type {number} */ + validBitsPerSample: 0, + /** @type {number} */ + dwChannelMask: 0, + /** + * 4 32-bit values representing a 128-bit ID + * @type {!Array<number>} + */ + subformat: [] }; /** * The data of the "fact" chunk. * @type {!Object<string, *>} */ WaveFile.fact = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - dwSampleLength: 0 + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + dwSampleLength: 0 }; /** * The data of the "cue " chunk. * @type {!Object<string, *>} */ WaveFile.cue = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - dwCuePoints: 0, - /** @type {!Array<!Object>} */ - points: [], + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + dwCuePoints: 0, + /** @type {!Array<!Object>} */ + points: [] }; /** * The data of the "smpl" chunk. * @type {!Object<string, *>} */ WaveFile.smpl = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - dwManufacturer: 0, - /** @type {number} */ - dwProduct: 0, - /** @type {number} */ - dwSamplePeriod: 0, - /** @type {number} */ - dwMIDIUnityNote: 0, - /** @type {number} */ - dwMIDIPitchFraction: 0, - /** @type {number} */ - dwSMPTEFormat: 0, - /** @type {number} */ - dwSMPTEOffset: 0, - /** @type {number} */ - dwNumSampleLoops: 0, - /** @type {number} */ - dwSamplerData: 0, - /** @type {!Array<!Object>} */ - loops: [], + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + dwManufacturer: 0, + /** @type {number} */ + dwProduct: 0, + /** @type {number} */ + dwSamplePeriod: 0, + /** @type {number} */ + dwMIDIUnityNote: 0, + /** @type {number} */ + dwMIDIPitchFraction: 0, + /** @type {number} */ + dwSMPTEFormat: 0, + /** @type {number} */ + dwSMPTEOffset: 0, + /** @type {number} */ + dwNumSampleLoops: 0, + /** @type {number} */ + dwSamplerData: 0, + /** @type {!Array<!Object>} */ + loops: [] }; /** * The data of the "bext" chunk. * @type {!Object<string, *>} */ WaveFile.bext = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {string} */ - description: '', //256 - /** @type {string} */ - originator: '', //32 - /** @type {string} */ - originatorReference: '', //32 - /** @type {string} */ - originationDate: '', //10 - /** @type {string} */ - originationTime: '', //8 - /** - * 2 32-bit values, timeReference high and low - * @type {!Array<number>} - */ - timeReference: [0, 0], - /** @type {number} */ - version: 0, //WORD - /** @type {string} */ - UMID: '', // 64 chars - /** @type {number} */ - loudnessValue: 0, //WORD - /** @type {number} */ - loudnessRange: 0, //WORD - /** @type {number} */ - maxTruePeakLevel: 0, //WORD - /** @type {number} */ - maxMomentaryLoudness: 0, //WORD - /** @type {number} */ - maxShortTermLoudness: 0, //WORD - /** @type {string} */ - reserved: '', //180 - /** @type {string} */ - codingHistory: '' // string, unlimited + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {string} */ + description: "", //256 + /** @type {string} */ + originator: "", //32 + /** @type {string} */ + originatorReference: "", //32 + /** @type {string} */ + originationDate: "", //10 + /** @type {string} */ + originationTime: "", //8 + /** + * 2 32-bit values, timeReference high and low + * @type {!Array<number>} + */ + timeReference: [0, 0], + /** @type {number} */ + version: 0, //WORD + /** @type {string} */ + UMID: "", // 64 chars + /** @type {number} */ + loudnessValue: 0, //WORD + /** @type {number} */ + loudnessRange: 0, //WORD + /** @type {number} */ + maxTruePeakLevel: 0, //WORD + /** @type {number} */ + maxMomentaryLoudness: 0, //WORD + /** @type {number} */ + maxShortTermLoudness: 0, //WORD + /** @type {string} */ + reserved: "", //180 + /** @type {string} */ + codingHistory: "" // string, unlimited +}; +/** + * The data of the 'mext' chunk. + * @type {!Object<string, *>} + */ +WaveFile.mext = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + soundInformation: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + ancillaryDataLength: 0, + /** @type {number} */ + ancillaryDataDef: 0, //4 + /** @type {string} */ + reserved: "" +}; +/** + * mpegInfo for making a wav from mpeg audio + * @type {!Object<string, *>} + */ +WaveFile.mpegInfo = { + /** @type {number} */ + version: 0, + /** @type {number} */ + layer: 0, + /** @type {number} */ + sampleRate: 0, + /** @type {number} */ + bitRate: 0, + /** @type {string} */ + channelMode: "", + /** @type {number} */ + padding: 0, + /** @type {number} */ + modeExtension: 0, + /** @type {number} */ + emphasis: 0, + /** @type {number} */ + privateBit: 0, + /** @type {boolean} */ + copyright: false, + /** @type {boolean} */ + original: false, + /** @type {boolean} */ + errorProtection: false, + /** @type {number} */ + numChannels: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + sampleLength: 0, + /** @type {boolean} */ + freeForm: false +}; +/** + * The data of the cart chunk. + * @type {!Object<string, *>} + */ +WaveFile.cart = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {string} */ + version: "", + /** @type {string} */ + title: "", + /** @type {string} */ + artist: "", + /** @type {string} */ + cutId: "", + /** @type {string} */ + clientId: "", + /** @type {string} */ + category: "", + /** @type {string} */ + classification: "", + /** @type {string} */ + outCue: "", + /** @type {string} */ + startDate: "", + /** @type {string} */ + startTime: "", + /** @type {string} */ + endDate: "", + /** @type {string} */ + endTime: "", + /** @type {string} */ + producerAppId: "", + /** @type {string} */ + producerAppVersion: "", + /** @type {string} */ + userDef: "", + /** @type {number} */ + levelReference: 0, + /** @type {string} */ + postTimer: "", + /** @type {string} */ + reserved: "", + /** @type {string} */ + url: "", + /** @type {string} */ + tagText: "" }; /** * The data of the 'iXML' chunk. @@ -1010,11 +1179,11 @@

    The WaveFile properties

    */ WaveFile.iXML = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - value: '' + value: "" }; /** * The data of the "ds64" chunk. @@ -1022,40 +1191,40 @@

    The WaveFile properties

    * @type {!Object<string, *>} */ WaveFile.ds64 = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {number} */ - riffSizeHigh: 0, // DWORD - /** @type {number} */ - riffSizeLow: 0, // DWORD - /** @type {number} */ - dataSizeHigh: 0, // DWORD - /** @type {number} */ - dataSizeLow: 0, // DWORD - /** @type {number} */ - originationTime: 0, // DWORD - /** @type {number} */ - sampleCountHigh: 0, // DWORD - /** @type {number} */ - sampleCountLow: 0, // DWORD - /** @type {number} */ - //"tableLength": 0, // DWORD - /** @type {!Array<number>} */ - //"table": [] + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + riffSizeHigh: 0, // DWORD + /** @type {number} */ + riffSizeLow: 0, // DWORD + /** @type {number} */ + dataSizeHigh: 0, // DWORD + /** @type {number} */ + dataSizeLow: 0, // DWORD + /** @type {number} */ + originationTime: 0, // DWORD + /** @type {number} */ + sampleCountHigh: 0, // DWORD + /** @type {number} */ + sampleCountLow: 0 // DWORD + /** @type {number} */ + //"tableLength": 0, // DWORD + /** @type {!Array<number>} */ + //"table": [] }; /** * The data of the "data" chunk. * @type {!Object<string, *>} */ WaveFile.data = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {!Uint8Array} */ - samples: new Uint8Array(0) + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {!Uint8Array} */ + samples: new Uint8Array(0) }; /** * The data of the "LIST" chunks. @@ -1074,12 +1243,12 @@

    The WaveFile properties

    * @type {!Object<string, *>} */ WaveFile.junk = { - /** @type {string} */ - chunkId: '', - /** @type {number} */ - chunkSize: 0, - /** @type {!Array<number>} */ - chunkData: [] + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {!Array<number>} */ + chunkData: [] }; /** * The data of the '_PMX' chunk. @@ -1087,17 +1256,17 @@

    The WaveFile properties

    */ WaveFile._PMX = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - value: '' + value: "" }; /** * The bit depth code according to the samples. * @type {string} */ -WaveFile.bitDepth = ''; +WaveFile.bitDepth = "";

    Cue points

    Items in cue.points are objects like this:

    @@ -1174,6 +1343,15 @@

    Style guide

    https://google.github.io/styleguide/jsguide.html

    Code of conduct

    This project is bound by a Code of Conduct: The Contributor Covenant, version 1.4, also available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

    +

    Creator's note

    +

    The creator of wavefile, @rochars, added this note to the README on the original project:

    +

    MOVING AWAY FROM GITHUB (2020-03-08)

    +

    Microsoft, owner of GitHub, was one of the main backers of the current fascist regime in Brazil and also of the coup d'etat that led to the present situation of my country.

    +

    It paid well: The brazilian government was required to run all its systems on open-source software. After the coup d'etat this changed, the goverment began purchasing Microsoft licenses and migrating all their systems to Windows.

    +

    It is not just a case of business malpractice - there is a genocide going on in Brazil and many people, including myself, have lived under constant death threats for the past couple years bacause of our positions against the current fascist regime. Many have been murdered or incarcerated. Poverty and violence skyrocketed.

    +

    This software will keep being released in NPM as always - only the repository will be moved. Projects depending on this software will not be affected.

    +

    For Microsoft owners and collaborators: you have a lot of blood in your hands. I will not share my work with people of your kind.

    +

    References

    Papers

    https://tech.ebu.ch/docs/tech/tech3285.pdf
    @@ -1183,11 +1361,15 @@

    Papers

    http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf
    https://sites.google.com/site/musicgapi/technical-documents/wav-file-format
    http://www.neurophys.wisc.edu/auditory/riff-format.txt
    -https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info

    +https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info +http://tech.ebu.ch/docs/tech/tech3285s1.pdf +http://www.cartchunk.org/ +http://www.aes.org/publications/standards/search.cfm?docID=41

    Software

    https://github.com/erikd/libsndfile
    https://gist.github.com/hackNightly/3776503
    -https://github.com/chirlu/sox/blob/master/src/wav.c

    +https://github.com/chirlu/sox/blob/master/src/wav.c +https://github.com/kookster/nu_wav

    Other

    https://developercertificate.org/
    https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
    diff --git a/docs/index.js.html b/docs/index.js.html index e4513ad..3a84515 100644 --- a/docs/index.js.html +++ b/docs/index.js.html @@ -27,7 +27,7 @@

    diff --git a/docs/lib_wavefile-converter.js.html b/docs/lib_wavefile-converter.js.html index a6cbfd2..3eaa2ed 100644 --- a/docs/lib_wavefile-converter.js.html +++ b/docs/lib_wavefile-converter.js.html @@ -27,7 +27,7 @@
    diff --git a/docs/lib_wavefile-creator.js.html b/docs/lib_wavefile-creator.js.html index 61b3697..64adcd0 100644 --- a/docs/lib_wavefile-creator.js.html +++ b/docs/lib_wavefile-creator.js.html @@ -27,7 +27,7 @@
    @@ -71,31 +71,30 @@

    lib/wavefile-creator.js

    * @see https://github.com/rochars/wavefile */ -import { WaveFileParser } from './wavefile-parser'; -import { interleave, deInterleave } from './parsers/interleave'; -import { validateNumChannels } from './validators/validate-num-channels'; -import { validateSampleRate } from './validators/validate-sample-rate'; -import { packArrayTo, unpackArrayTo, packTo, unpack } from './parsers/binary'; - +import { WaveFileParser } from "./wavefile-parser"; +import { interleave, deInterleave } from "./parsers/interleave"; +import { validateNumChannels } from "./validators/validate-num-channels"; +import { validateSampleRate } from "./validators/validate-sample-rate"; +import { packArrayTo, unpackArrayTo, packTo, unpack } from "./parsers/binary"; +import { MpegReader } from "./mpeg-reader"; /** * A class to read, write and create wav files. * @extends WaveFileParser * @ignore */ export class WaveFileCreator extends WaveFileParser { - constructor() { super(); /** * The bit depth code according to the samples. * @type {string} */ - this.bitDepth = '0'; + this.bitDepth = "0"; /** * @type {!{bits: number, be: boolean}} * @protected */ - this.dataType = {bits: 0, be: false}; + this.dataType = { bits: 0, be: false }; /** * Audio formats. * Formats not listed here should be set to 65534, @@ -104,15 +103,16 @@

    lib/wavefile-creator.js

    * @protected */ this.WAV_AUDIO_FORMATS = { - '4': 17, - '8': 1, - '8a': 6, - '8m': 7, - '16': 1, - '24': 1, - '32': 1, - '32f': 3, - '64': 3 + "4": 17, + "8": 1, + "8a": 6, + "8m": 7, + "16": 1, + "24": 1, + "32": 1, + "32f": 3, + "64": 3, + "65535": 80 // mpeg == 80 }; } @@ -137,6 +137,182 @@

    lib/wavefile-creator.js

    this.newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options); } + /** + * Set up the WaveFileCreator object from an mpeg buffer and metadata info. + * @param {!Uint8Array} mpegBuffer The buffer. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * Required mpeg info: + * info.version + * info.layer + * info.errorProtection + * info.bitRate + * info.sampleRate + * info.padding + * info.privateBit + * info.channelMode + * info.modeExtension + * info.copyright + * info.original + * info.emphasis + * info.numChannels + * info.frameSize + * info.sampleLength + * @throws {Error} If any argument does not meet the criteria. + */ + fromMpeg(mpegBuffer, info = null) { + this.clearHeaders(); + + if (info == null) { + info = new MpegReader(mpegBuffer); + } + + let codingHistory = this.mpegCodingHistory_(info); + + // riff(4) + fmt(40+8) + mext(12+8) + bxt(602+8+codingHistory.length) + fact(4+8) + buffer.length + // 4 + 48 + 20 + 610 + 12 + codingHistory.length + buffer.length + this.container = "RIFF"; + this.chunkSize = 694 + codingHistory.length + mpegBuffer.length; + this.format = "WAVE"; + this.bitDepth = "65535"; + + this.fmt.chunkId = "fmt "; + this.fmt.chunkSize = 40; + this.fmt.audioFormat = 80; + this.fmt.numChannels = info.numChannels; + this.fmt.sampleRate = info.sampleRate; + this.fmt.byteRate = (info.bitRate / 8) * 1000; + this.fmt.blockAlign = info.frameSize; + this.fmt.bitsPerSample = 65535; + this.fmt.cbSize = 22; + this.fmt.headLayer = Math.pow(2, info.layer - 1); + this.fmt.headBitRate = info.bitRate * 1000; + this.fmt.headMode = this.mpegHeadMode_(info); + this.fmt.headModeExt = this.mpegHeadModeExt_(info); + this.fmt.headEmphasis = info.emphasis + 1; + this.fmt.headFlags = this.mpegHeadFlags_(info); + this.fmt.ptsLow = 0; + this.fmt.ptsHigh = 0; + + this.mext.chunkId = "mext"; + this.mext.chunkSize = 12; + this.mext.soundInformation = this.mpegSoundInformation_(info); + this.mext.frameSize = info.frameSize; + this.mext.ancillaryDataLength = 0; + this.mext.ancillaryDataDef = 0; + this.mext.reserved = ""; + + this.bext.chunkId = "bext"; + this.bext.chunkSize = 602 + codingHistory.length; + this.bext.timeReference = [0, 0]; + this.bext.version = 1; + this.bext.codingHistory = codingHistory; + + this.fact.chunkId = "fact"; + this.fact.chunkSize = 4; + this.fact.dwSampleLength = info.sampleLength; + + this.data.chunkId = "data"; + this.data.samples = mpegBuffer; + this.data.chunkSize = this.data.samples.length; + } + + mpegSoundInformation_(info) { + let soundInformation = 0; + if (info.homogeneous) { + soundInformation += 1; + } + if (!info.padding) { + soundInformation += 2; + } + if ( + (info.sampleRate == 44100 || info.sampleRate == 22050) && + !info.padding + ) { + soundInformation += 4; + } + if (info.freeForm) { + soundInformation += 8; + } + return soundInformation; + } + + /** + * Returns the mode value based on the channel mode of the mpeg file + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ + // prettier-ignore + mpegHeadMode_(info) { + return { + "stereo": 1, + "joint-stereo": 2, + "dual-mono": 4, + "mono": 8 + }[info.channelMode]; + } + + /** + * Contains extra parameters for joint–stereo coding; not used for other modes. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ + mpegHeadModeExt_(info) { + if (info.channelMode == "joint-stereo") { + return Math.pow(2, info.modeExtension); + } else { + return 0; + } + } + + /** + * Follows EBU established standards for CodingHistory for MPEG + * EBU Technical Recommendation R98-1999, https://tech.ebu.ch/docs/r/r098.pdf + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + **/ + mpegCodingHistory_(info) { + return ( + "A=MPEG" + + info.version + + "L" + + info.layer + + ",F=" + + info.sampleRate + + ",B=" + + info.bitRate + + ",M=" + + info.channelMode + + ",T=wavefile\r\n\0\0" + ); + } + + /** + * Follows EBU standards for `fmt` chunk `fwHeadFlags` for MPEG in BWF + * EBU Tech. 3285–E – Supplement 1, 1997 + * https://tech.ebu.ch/docs/tech/tech3285s1.pdf + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + **/ + mpegHeadFlags_(info) { + let flags = 0; + if (info.privateBit) { + flags += 1; + } + if (info.copyright) { + flags += 2; + } + if (info.original) { + flags += 4; + } + if (info.errorProtection) { + flags += 8; + } + if (info.version > 0) { + flags += 16; + } + return flags; + } + /** * Set up the WaveFileParser object from a byte buffer. * @param {!Uint8Array} wavBuffer The buffer. @@ -146,7 +322,7 @@

    lib/wavefile-creator.js

    * @throws {Error} If no 'fmt ' chunk is found. * @throws {Error} If no 'data' chunk is found. */ - fromBuffer(wavBuffer, samples=true) { + fromBuffer(wavBuffer, samples = true) { super.fromBuffer(wavBuffer, samples); this.bitDepthFromFmt_(); this.updateDataType_(); @@ -172,17 +348,23 @@

    lib/wavefile-creator.js

    * @param {Function=} [OutputObject=Float64Array] The sample container. * @return {!(Array|TypedArray)} the samples. */ - getSamples(interleaved=false, OutputObject=Float64Array) { + getSamples(interleaved = false, OutputObject = Float64Array) { /** * A Float64Array created with a size to match the * the length of the samples. * @type {!(Array|TypedArray)} */ let samples = new OutputObject( - this.data.samples.length / (this.dataType.bits / 8)); + this.data.samples.length / (this.dataType.bits / 8) + ); // Unpack all the samples - unpackArrayTo(this.data.samples, this.dataType, samples, - 0, this.data.samples.length); + unpackArrayTo( + this.data.samples, + this.dataType, + samples, + 0, + this.data.samples.length + ); if (!interleaved && this.fmt.numChannels > 1) { return deInterleave(samples, this.fmt.numChannels, OutputObject); } @@ -198,11 +380,12 @@

    lib/wavefile-creator.js

    getSample(index) { index = index * (this.dataType.bits / 8); if (index + this.dataType.bits / 8 > this.data.samples.length) { - throw new Error('Range error'); + throw new Error("Range error"); } return unpack( this.data.samples.slice(index, index + this.dataType.bits / 8), - this.dataType); + this.dataType + ); } /** @@ -214,7 +397,7 @@

    lib/wavefile-creator.js

    setSample(index, sample) { index = index * (this.dataType.bits / 8); if (index + this.dataType.bits / 8 > this.data.samples.length) { - throw new Error('Range error'); + throw new Error("Range error"); } packTo(sample, this.dataType, this.data.samples, index, true); } @@ -233,11 +416,11 @@

    lib/wavefile-creator.js

    * @throws {TypeError} If the value is not a string. */ setiXML(iXMLValue) { - if (typeof iXMLValue !== 'string') { - throw new TypeError('iXML value must be a string.'); + if (typeof iXMLValue !== "string") { + throw new TypeError("iXML value must be a string."); } this.iXML.value = iXMLValue; - this.iXML.chunkId = 'iXML'; + this.iXML.chunkId = "iXML"; } /** @@ -254,11 +437,11 @@

    lib/wavefile-creator.js

    * @throws {TypeError} If the value is not a string. */ set_PMX(_PMXValue) { - if (typeof _PMXValue !== 'string') { - throw new TypeError('_PMX value must be a string.'); + if (typeof _PMXValue !== "string") { + throw new TypeError("_PMX value must be a string."); } this._PMX.value = _PMXValue; - this._PMX.chunkId = '_PMX'; + this._PMX.chunkId = "_PMX"; } /** @@ -276,7 +459,7 @@

    lib/wavefile-creator.js

    */ newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options) { if (!options.container) { - options.container = 'RIFF'; + options.container = "RIFF"; } this.container = options.container; this.bitDepth = bitDepthCode; @@ -287,9 +470,14 @@

    lib/wavefile-creator.js

    this.data.samples = new Uint8Array(samples.length * numBytes); packArrayTo(samples, this.dataType, this.data.samples, 0, true); this.makeWavHeader_( - bitDepthCode, numChannels, sampleRate, - numBytes, this.data.samples.length, options); - this.data.chunkId = 'data'; + bitDepthCode, + numChannels, + sampleRate, + numBytes, + this.data.samples.length, + options + ); + this.data.chunkId = "data"; this.data.chunkSize = this.data.samples.length; this.validateWavHeader_(); } @@ -305,23 +493,52 @@

    lib/wavefile-creator.js

    * @private */ makeWavHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { - if (bitDepthCode == '4') { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { + if (bitDepthCode == "4") { this.createADPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); - - } else if (bitDepthCode == '8a' || bitDepthCode == '8m') { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); + } else if (bitDepthCode == "8a" || bitDepthCode == "8m") { this.createALawMulawHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); - - } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 || - numChannels > 2) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); + } else if ( + Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 || + numChannels > 2 + ) { this.createExtensibleHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); - + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); } else { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); } } @@ -336,18 +553,24 @@

    lib/wavefile-creator.js

    * @private */ createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.container = options.container; this.chunkSize = 36 + samplesLength; - this.format = 'WAVE'; + this.format = "WAVE"; this.bitDepth = bitDepthCode; this.fmt = { - chunkId: 'fmt ', + chunkId: "fmt ", chunkSize: 16, audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534, numChannels: numChannels, sampleRate: sampleRate, - byteRate: (numChannels * numBytes) * sampleRate, + byteRate: numChannels * numBytes * sampleRate, blockAlign: numChannels * numBytes, bitsPerSample: parseInt(bitDepthCode, 10), cbSize: 0, @@ -368,9 +591,21 @@

    lib/wavefile-creator.js

    * @private */ createADPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); this.chunkSize = 40 + samplesLength; this.fmt.chunkSize = 20; this.fmt.byteRate = 4055; @@ -379,7 +614,7 @@

    lib/wavefile-creator.js

    this.fmt.cbSize = 2; this.fmt.validBitsPerSample = 505; this.fact = { - chunkId: 'fact', + chunkId: "fact", chunkSize: 4, dwSampleLength: samplesLength * 2 }; @@ -396,9 +631,21 @@

    lib/wavefile-creator.js

    * @private */ createExtensibleHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); this.chunkSize = 36 + 24 + samplesLength; this.fmt.chunkSize = 40; this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1; @@ -421,15 +668,27 @@

    lib/wavefile-creator.js

    * @private */ createALawMulawHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); this.chunkSize = 40 + samplesLength; this.fmt.chunkSize = 20; this.fmt.cbSize = 2; this.fmt.validBitsPerSample = 8; this.fact = { - chunkId: 'fact', + chunkId: "fact", chunkSize: 4, dwSampleLength: samplesLength }; @@ -441,11 +700,13 @@

    lib/wavefile-creator.js

    */ bitDepthFromFmt_() { if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) { - this.bitDepth = '32f'; + this.bitDepth = "32f"; } else if (this.fmt.audioFormat === 6) { - this.bitDepth = '8a'; + this.bitDepth = "8a"; } else if (this.fmt.audioFormat === 7) { - this.bitDepth = '8m'; + this.bitDepth = "8m"; + } else if (this.fmt.audioFormat === 80) { + this.bitDepth = "65535"; } else { this.bitDepth = this.fmt.bitsPerSample.toString(); } @@ -459,11 +720,10 @@

    lib/wavefile-creator.js

    */ validateBitDepth_() { if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) { - if (parseInt(this.bitDepth, 10) > 8 && - parseInt(this.bitDepth, 10) < 54) { + if (parseInt(this.bitDepth, 10) > 8 && parseInt(this.bitDepth, 10) < 54) { return true; } - throw new Error('Invalid bit depth.'); + throw new Error("Invalid bit depth."); } return true; } @@ -475,11 +735,11 @@

    lib/wavefile-creator.js

    updateDataType_() { this.dataType = { bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1, - fp: this.bitDepth == '32f' || this.bitDepth == '64', - signed: this.bitDepth != '8', - be: this.container == 'RIFX' + fp: this.bitDepth == "32f" || this.bitDepth == "64", + signed: this.bitDepth != "8", + be: this.container == "RIFX" }; - if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) { + if (["4", "8a", "8m"].indexOf(this.bitDepth) > -1) { this.dataType.bits = 8; this.dataType.signed = false; } @@ -496,11 +756,16 @@

    lib/wavefile-creator.js

    validateWavHeader_() { this.validateBitDepth_(); if (!validateNumChannels(this.fmt.numChannels, this.fmt.bitsPerSample)) { - throw new Error('Invalid number of channels.'); + throw new Error("Invalid number of channels."); } - if (!validateSampleRate( - this.fmt.numChannels, this.fmt.bitsPerSample, this.fmt.sampleRate)) { - throw new Error('Invalid sample rate.'); + if ( + !validateSampleRate( + this.fmt.numChannels, + this.fmt.bitsPerSample, + this.fmt.sampleRate + ) + ) { + throw new Error("Invalid sample rate."); } } } @@ -517,18 +782,18 @@

    lib/wavefile-creator.js

    // mono = FC if (numChannels === 1) { mask = 0x4; - // stereo = FL, FR + // stereo = FL, FR } else if (numChannels === 2) { mask = 0x3; - // quad = FL, FR, BL, BR + // quad = FL, FR, BL, BR } else if (numChannels === 4) { mask = 0x33; - // 5.1 = FL, FR, FC, LF, BL, BR + // 5.1 = FL, FR, FC, LF, BL, BR } else if (numChannels === 6) { - mask = 0x3F; - // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR + mask = 0x3f; + // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR } else if (numChannels === 8) { - mask = 0x63F; + mask = 0x63f; } return mask; } diff --git a/docs/lib_wavefile-cue-editor.js.html b/docs/lib_wavefile-cue-editor.js.html index bb75d59..0bd95cc 100644 --- a/docs/lib_wavefile-cue-editor.js.html +++ b/docs/lib_wavefile-cue-editor.js.html @@ -27,7 +27,7 @@
    diff --git a/docs/lib_wavefile-tag-editor.js.html b/docs/lib_wavefile-tag-editor.js.html index ca75540..6f0f9eb 100644 --- a/docs/lib_wavefile-tag-editor.js.html +++ b/docs/lib_wavefile-tag-editor.js.html @@ -27,7 +27,7 @@
    diff --git a/docs/module-wavefile.WaveFile.html b/docs/module-wavefile.WaveFile.html index c6be623..a8f4ac5 100644 --- a/docs/module-wavefile.WaveFile.html +++ b/docs/module-wavefile.WaveFile.html @@ -27,7 +27,7 @@
    @@ -357,7 +357,7 @@

    bitDepthSource:
    @@ -435,7 +435,7 @@

    (protected, non-null
    Source:
    @@ -509,7 +509,7 @@

    (protected)
    Source:
    @@ -777,6 +777,29 @@
    Properties:
    + + + + + + + + 65535 + + + + + +number + + + + + + + + + @@ -790,7 +813,9 @@
    Properties:
    - Audio formats. Formats not listed here should be set to 65534, the code for WAVE_FORMAT_EXTENSIBLE + Audio formats. +Formats not listed here should be set to 65534, +the code for WAVE_FORMAT_EXTENSIBLE
    @@ -932,7 +957,8 @@
    Parameters:
    - the index of the point. First is 1, second is 2, and so on. + the index of the point. First is 1, + second is 2, and so on. @@ -1256,7 +1282,8 @@
    Parameters:
    - The new bit depth of the samples. One of '8' ... '32' (integers), '32f' or '64' (floats). + The new bit depth of the samples. + One of '8' ... '32' (integers), '32f' or '64' (floats). @@ -1463,7 +1490,7 @@

    fromBuffer<
    Source:
    @@ -2053,7 +2080,220 @@

    Parameters:
    - The new bit depth of the samples. One of '8' ... '32' (integers), '32f' or '64' (floats). + The new bit depth of the samples. + One of '8' ... '32' (integers), '32f' or '64' (floats). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    fromMpeg(mpegBuffernon-null, infoopt)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + Set up the WaveFileCreator object from an mpeg buffer and metadata info. +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2073,6 +2313,35 @@
    Parameters:
    +
    Throws:
    + + + +
    +
    +
    + If any argument does not meet the criteria. +
    +
    +
    +
    +
    +
    + Type +
    +
    + +Error + + +
    +
    +
    +
    +
    + + + @@ -2214,7 +2483,8 @@
    Parameters:
    - + @@ -2301,7 +2571,8 @@

    fromScratc
    - Set up the WaveFileCreator object based on the arguments passed. Existing chunks are reset. + Set up the WaveFileCreator object based on the arguments passed. +Existing chunks are reset.
    @@ -2396,7 +2667,8 @@

    Parameters:
    - + @@ -2427,7 +2699,9 @@
    Parameters:
    - + @@ -2494,7 +2768,8 @@
    Parameters:
    - + @@ -2565,7 +2840,7 @@

    get_PMXSource:
    @@ -2679,7 +2954,7 @@

    getiXMLSource:
    @@ -2952,7 +3227,7 @@

    getSampleSource:
    @@ -3144,7 +3419,7 @@

    getSamples<
    Source:
    @@ -3263,7 +3538,8 @@

    Parameters:
    -

    + @@ -3577,7 +3853,26 @@

    listCueP
    - Return an array with all cue points in the file, in the order they appear in the file. Objects representing cue points/regions look like this: { position: 500, // the position in milliseconds label: 'cue marker 1', end: 1500, // the end position in milliseconds dwName: 1, dwPosition: 0, fccChunk: 'data', dwChunkStart: 0, dwBlockStart: 0, dwSampleOffset: 22050, // the position as a sample offset dwSampleLength: 3646827, // length as a sample count, 0 if not a region dwPurposeID: 544106354, dwCountry: 0, dwLanguage: 0, dwDialect: 0, dwCodePage: 0, } + Return an array with all cue points in the file, in the order they appear +in the file. +Objects representing cue points/regions look like this: + { + position: 500, // the position in milliseconds + label: 'cue marker 1', + end: 1500, // the end position in milliseconds + dwName: 1, + dwPosition: 0, + fccChunk: 'data', + dwChunkStart: 0, + dwBlockStart: 0, + dwSampleOffset: 22050, // the position as a sample offset + dwSampleLength: 3646827, // length as a sample count, 0 if not a region + dwPurposeID: 544106354, + dwCountry: 0, + dwLanguage: 0, + dwDialect: 0, + dwCodePage: 0, + }
    @@ -3744,7 +4039,7 @@

    Returns:
    -

    set_PMX(_PMXValue)

    +

    mpegCodingHistory_(infoopt)

    @@ -3756,7 +4051,7 @@

    set_PMXSource:
    @@ -3769,7 +4064,7 @@

    set_PMXOverrides:
    @@ -3801,7 +4096,8 @@

    set_PMX - Set the value of the _PMX chunk. + Follows EBU established standards for CodingHistory for MPEG +EBU Technical Recommendation R98-1999, https://tech.ebu.ch/docs/r/r098.pdf @@ -3827,6 +4123,8 @@
    Parameters:

    + + @@ -3839,23 +4137,751 @@
    Parameters:
    - + + - + + + + + + + +
    NameTypeAttributesDefaultDescription
    mpegBuffer + + +Uint8Array + + + + + + + + + + + + The buffer.
    info + + +Object + + + + + + <optional>
    + + + + + +
    + + null + + Mpeg info such as version, layer, bitRate, etc. +Required mpeg info: +info.version +info.layer +info.errorProtection +info.bitRate +info.sampleRate +info.padding +info.privateBit +info.channelMode +info.modeExtension +info.copyright +info.original +info.emphasis +info.numChannels +info.frameSize +info.sampleLength
    The new bit depth of the samples. One of '8' ... '32' (integers), '32f' or '64' (floats).The new bit depth of the samples. + One of '8' ... '32' (integers), '32f' or '64' (floats).
    The sample rate. Integers like 8000, 44100, 48000, 96000, 192000.The sample rate. + Integers like 8000, 44100, 48000, 96000, 192000.
    The audio bit depth code. One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64' or any value between '8' and '32' (like '12').The audio bit depth code. + One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64' + or any value between '8' and '32' (like '12').
    Optional. Used to force the container as RIFX with {'container': 'RIFX'}Optional. Used to force the container + as RIFX with {'container': 'RIFX'}
    True to return interleaved samples, false to return the samples de-interleaved.True to return interleaved samples, + false to return the samples de-interleaved.
    TypeAttributes
    _PMXValueinfo -string +Object + + <optional>
    + + + + +
    The value for the _PMX chunk.Mpeg info such as version, layer, bitRate, etc.
    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    +
    +
    + If any argument does not meet the criteria. +
    +
    +
    +
    +
    +
    + Type +
    +
    + +Error + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + +

    mpegHeadFlags_(infoopt)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + Follows EBU standards for `fmt` chunk `fwHeadFlags` for MPEG in BWF +EBU Tech. 3285–E – Supplement 1, 1997 +https://tech.ebu.ch/docs/tech/tech3285s1.pdf +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    info + + +Object + + + + + + <optional>
    + + + + + +
    Mpeg info such as version, layer, bitRate, etc.
    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    +
    +
    + If any argument does not meet the criteria. +
    +
    +
    +
    +
    +
    + Type +
    +
    + +Error + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + +

    mpegHeadMode_(infoopt)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + Returns the mode value based on the channel mode of the mpeg file +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    info + + +Object + + + + + + <optional>
    + + + + + +
    Mpeg info such as version, layer, bitRate, etc.
    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    +
    +
    + If any argument does not meet the criteria. +
    +
    +
    +
    +
    +
    + Type +
    +
    + +Error + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + +

    mpegHeadModeExt_(infoopt)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + Contains extra parameters for joint–stereo coding; not used for other modes. +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    info + + +Object + + + + + + <optional>
    + + + + + +
    Mpeg info such as version, layer, bitRate, etc.
    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    +
    +
    + If any argument does not meet the criteria. +
    +
    +
    +
    +
    +
    + Type +
    +
    + +Error + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + +

    set_PMX(_PMXValue)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + Set the value of the _PMX chunk. +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4025,7 +5051,25 @@
    Parameters:
    - + @@ -4067,7 +5111,7 @@

    setiXMLSource:
    @@ -4237,7 +5281,7 @@

    setSampleSource:
    @@ -4475,7 +5519,8 @@

    setTag - Write a RIFF tag in the INFO chunk. If the tag do not exist, then it is created. It if exists, it is overwritten. + Write a RIFF tag in the INFO chunk. If the tag do not exist, +then it is created. It if exists, it is overwritten. @@ -4968,7 +6013,8 @@
    Parameters:
    -

    + @@ -5007,7 +6053,8 @@
    Parameters:
    - + @@ -5078,7 +6125,7 @@

    toBufferSource:
    @@ -5123,7 +6170,8 @@

    toBuffer - Return a byte buffer representig the WaveFileParser object as a .wav file. The return value of this method can be written straight to disk. + Return a byte buffer representig the WaveFileParser object as a .wav file. +The return value of this method can be written straight to disk. @@ -5315,7 +6363,8 @@

    toDataURI - Return a DataURI string representig the WaveFile object as a .wav file. The return of this method can be used to load the audio in browsers. + Return a DataURI string representig the WaveFile object as a .wav file. +The return of this method can be used to load the audio in browsers. diff --git a/docs/module-wavefile.html b/docs/module-wavefile.html index ffdb2c9..629b818 100644 --- a/docs/module-wavefile.html +++ b/docs/module-wavefile.html @@ -27,7 +27,7 @@
    diff --git a/externs/wavefile.js b/externs/wavefile.js index 4fe9f25..9e93ead 100644 --- a/externs/wavefile.js +++ b/externs/wavefile.js @@ -39,7 +39,7 @@ var WaveFile = {}; * RIFF, RIFX and RF64 are supported. * @type {string} */ -WaveFile.prototype.container = ''; +WaveFile.prototype.container = ""; /** * @type {number} */ @@ -49,14 +49,14 @@ WaveFile.prototype.chunkSize = 0; * Always WAVE. * @type {string} */ -WaveFile.prototype.format = ''; +WaveFile.prototype.format = ""; /** * The data of the fmt chunk. * @type {!Object} */ WaveFile.prototype.fmt = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -81,7 +81,28 @@ WaveFile.prototype.fmt = { * 4 32-bit values representing a 128-bit ID * @type {!Array} */ - subformat: [] + subformat: [], + /** + * MPEG additional format information + * when audioFormat == 80 + * https://tech.ebu.ch/docs/tech/tech3285s1.pdf + */ + /** @type {number} */ + headLayer: 0, + /** @type {number} */ + headBitRate: 0, + /** @type {number} */ + headMode: 0, + /** @type {number} */ + headModeExt: 0, + /** @type {number} */ + headEmphasis: 0, + /** @type {number} */ + headFlags: 0, + /** @type {number} */ + ptsLow: 0, + /** @type {number} */ + ptsHigh: 0 }; /** * The data of the fact chunk. @@ -89,7 +110,7 @@ WaveFile.prototype.fmt = { */ WaveFile.prototype.fact = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -101,21 +122,23 @@ WaveFile.prototype.fact = { */ WaveFile.prototype.cue = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ dwCuePoints: 0, /** @type {!Array} */ - points: [{ - dwName: 0, // a cue point ID - dwPosition: 0, - fccChunk: 0, - dwChunkStart: 0, - dwBlockStart: 0, - dwSampleOffset: 0, - position: 0 - }], + points: [ + { + dwName: 0, // a cue point ID + dwPosition: 0, + fccChunk: 0, + dwChunkStart: 0, + dwBlockStart: 0, + dwSampleOffset: 0, + position: 0 + } + ] }; /** * The data of the smpl chunk. @@ -123,7 +146,7 @@ WaveFile.prototype.cue = { */ WaveFile.prototype.smpl = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -147,14 +170,14 @@ WaveFile.prototype.smpl = { /** @type {!Array} */ loops: [ { - dwName: '', // a cue point ID + dwName: "", // a cue point ID dwType: 0, dwStart: 0, dwEnd: 0, dwFraction: 0, dwPlayCount: 0 } - ], + ] }; /** * The data of the bext chunk. @@ -162,19 +185,19 @@ WaveFile.prototype.smpl = { */ WaveFile.prototype.bext = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - description: '', + description: "", /** @type {string} */ - originator: '', + originator: "", /** @type {string} */ - originatorReference: '', + originatorReference: "", /** @type {string} */ - originationDate: '', + originationDate: "", /** @type {string} */ - originationTime: '', + originationTime: "", /** * 2 32-bit values, timeReference high and low * @type {!Array} @@ -183,7 +206,7 @@ WaveFile.prototype.bext = { /** @type {number} */ version: 0, /** @type {string} */ - UMID: '', + UMID: "", /** @type {number} */ loudnessValue: 0, /** @type {number} */ @@ -195,9 +218,117 @@ WaveFile.prototype.bext = { /** @type {number} */ maxShortTermLoudness: 0, /** @type {string} */ - reserved: '', + reserved: "", + /** @type {string} */ + codingHistory: "" +}; +/** + * The data of the 'mext' chunk. + * @type {!Object} + */ +WaveFile.prototype.mext = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + soundInformation: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + ancillaryDataLength: 0, + /** @type {number} */ + ancillaryDataDef: 0, //4 + /** @type {string} */ + reserved: "" +}; +/** + * mpegInfo for making a wav from mpeg audio + * @type {!Object} + */ +WaveFile.prototype.mpegInfo = { + /** @type {number} */ + version: 0, + /** @type {number} */ + layer: 0, + /** @type {number} */ + sampleRate: 0, + /** @type {number} */ + bitRate: 0, + /** @type {string} */ + channelMode: "", + /** @type {number} */ + padding: 0, + /** @type {number} */ + modeExtension: 0, + /** @type {number} */ + emphasis: 0, + /** @type {number} */ + privateBit: 0, + /** @type {boolean} */ + copyright: false, + /** @type {boolean} */ + original: false, + /** @type {boolean} */ + errorProtection: false, + /** @type {number} */ + numChannels: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + sampleLength: 0, + /** @type {boolean} */ + freeForm: false +}; +/** + * The data of the cart chunk. + * @type {!Object} + */ +WaveFile.prototype.cart = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {string} */ + version: "", + /** @type {string} */ + title: "", + /** @type {string} */ + artist: "", + /** @type {string} */ + cutId: "", + /** @type {string} */ + clientId: "", + /** @type {string} */ + category: "", + /** @type {string} */ + classification: "", + /** @type {string} */ + outCue: "", /** @type {string} */ - codingHistory: '' + startDate: "", + /** @type {string} */ + startTime: "", + /** @type {string} */ + endDate: "", + /** @type {string} */ + endTime: "", + /** @type {string} */ + producerAppId: "", + /** @type {string} */ + producerAppVersion: "", + /** @type {string} */ + userDef: "", + /** @type {number} */ + levelReference: 0, + /** @type {string} */ + postTimer: "", + /** @type {string} */ + reserved: "", + /** @type {string} */ + url: "", + /** @type {string} */ + tagText: "" }; /** * The data of the iXML chunk. @@ -205,7 +336,7 @@ WaveFile.prototype.bext = { */ WaveFile.prototype.iXML = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -218,7 +349,7 @@ WaveFile.prototype.iXML = { */ WaveFile.prototype.ds64 = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -242,7 +373,7 @@ WaveFile.prototype.ds64 = { */ WaveFile.prototype.data = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {!Uint8Array} */ @@ -254,26 +385,26 @@ WaveFile.prototype.data = { */ WaveFile.prototype.LIST = [ { - chunkId: '', + chunkId: "", chunkSize: 0, - format: '', + format: "", subChunks: [ // For format 'INFO' { - chunkId: '', + chunkId: "", chunkSize: 0, - value: '' + value: "" }, // For format 'adtl' types 'labl' or 'note' { - chunkId: '', + chunkId: "", chunkSize: 0, dwName: 0, - value: '' + value: "" }, // For format 'adtl' type 'ltxt' { - chunkId: '', + chunkId: "", value: 0, dwName: 0, dwSampleLength: 0, @@ -292,7 +423,7 @@ WaveFile.prototype.LIST = [ */ WaveFile.prototype.junk = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {!Array} */ @@ -304,7 +435,7 @@ WaveFile.prototype.junk = { */ WaveFile.prototype._PMX = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -314,7 +445,13 @@ WaveFile.prototype._PMX = { * The bit depth code according to the samples. * @type {string} */ -WaveFile.prototype.bitDepth = ''; +WaveFile.prototype.bitDepth = ""; +/** + * Whether to apply a pad byte or not + * Defaults to 'true' + * @type {boolean} + */ +WaveFile.prototype.padBytes = true; /** * Return the samples packed in a Float64Array. @@ -324,8 +461,9 @@ WaveFile.prototype.bitDepth = ''; * @return {!(Array|TypedArray)} the samples. */ WaveFile.prototype.getSamples = function( - interleaved=false, - OutputObject=Float64Array) {}; + interleaved = false, + OutputObject = Float64Array +) {}; /** * Return the sample at a given index. @@ -358,8 +496,22 @@ WaveFile.prototype.setSample = function(index, sample) {}; * @throws {Error} If any argument does not meet the criteria. */ WaveFile.prototype.fromScratch = function( - numChannels, sampleRate, bitDepthCode, samples, options={ - container:'RIFF'}) {}; + numChannels, + sampleRate, + bitDepthCode, + samples, + options = { + container: "RIFF" + } +) {}; + +/** + * Set up the WaveFileCreator object from an mpeg buffer and metadata info. + * @param {!Uint8Array} mpegBuffer The buffer. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ +WaveFile.prototype.fromMpeg = function(mpegBuffer, info) {}; /** * Set up the WaveFileParser object from a byte buffer. @@ -370,7 +522,7 @@ WaveFile.prototype.fromScratch = function( * @throws {Error} If no 'fmt ' chunk is found. * @throws {Error} If no 'data' chunk is found. */ -WaveFile.prototype.fromBuffer = function(wavBuffer, samples=true) {}; +WaveFile.prototype.fromBuffer = function(wavBuffer, samples = true) {}; /** * Return a byte buffer representig the WaveFileParser object as a .wav file. @@ -429,7 +581,10 @@ WaveFile.prototype.toRIFX = function() {}; * resolution of samples should be actually changed or not. * @throws {Error} If the bit depth is not valid. */ -WaveFile.prototype.toBitDepth = function(newBitDepth, changeResolution=true) {}; +WaveFile.prototype.toBitDepth = function( + newBitDepth, + changeResolution = true +) {}; /** * Convert the sample rate of the file. @@ -437,16 +592,19 @@ WaveFile.prototype.toBitDepth = function(newBitDepth, changeResolution=true) {}; * @param {Object=} options The extra configuration, if needed. */ WaveFile.prototype.toSampleRate = function( - sampleRate, options= { - method: 'cubic', - clip: 'mirror', + sampleRate, + options = { + method: "cubic", + clip: "mirror", tension: 0, sincFilterSize: 32, lanczosFilterSize: 24, - sincWindow: function(x){}, + sincWindow: function(x) {}, LPF: true, - LPFType: 'IIR', - LPForder: 1}) {}; + LPFType: "IIR", + LPForder: 1 + } +) {}; /** * Encode a 16-bit wave file as 4-bit IMA ADPCM. @@ -460,7 +618,7 @@ WaveFile.prototype.toIMAADPCM = function() {}; * @param {string=} [bitDepthCode='16'] The new bit depth of the samples. * One of '8' ... '32' (integers), '32f' or '64' (floats). */ -WaveFile.prototype.fromIMAADPCM = function(bitDepthCode='16') {}; +WaveFile.prototype.fromIMAADPCM = function(bitDepthCode = "16") {}; /** * Encode 16-bit wave file as 8-bit A-Law. @@ -472,7 +630,7 @@ WaveFile.prototype.toALaw = function() {}; * @param {string=} [bitDepthCode='16'] The new bit depth of the samples. * One of '8' ... '32' (integers), '32f' or '64' (floats). */ -WaveFile.prototype.fromALaw = function(bitDepthCode='16') {}; +WaveFile.prototype.fromALaw = function(bitDepthCode = "16") {}; /** * Encode 16-bit wave file as 8-bit mu-Law. @@ -484,7 +642,7 @@ WaveFile.prototype.toMuLaw = function() {}; * @param {string=} [bitDepthCode='16'] The new bit depth of the samples. * One of '8' ... '32' (integers), '32f' or '64' (floats). */ -WaveFile.prototype.fromMuLaw = function(bitDepthCode='16') {}; +WaveFile.prototype.fromMuLaw = function(bitDepthCode = "16") {}; /** * Write a RIFF tag in the INFO chunk. If the tag do not exist, diff --git a/index.d.ts b/index.d.ts index 5a2a9cf..3424325 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,9 +6,7 @@ export = wavefile; declare module wavefile { - class WaveFile { - /** * The bit depth code according to the samples. * @type {string} @@ -19,7 +17,7 @@ declare module wavefile { * 'RIFF', 'RIFX' and 'RF64' are supported. * @type {string} */ - container: string; + container: 'RIFF' | 'RIFX' | 'RF64'; /** * @type {number} */ @@ -29,12 +27,12 @@ declare module wavefile { * Always 'WAVE'. * @type {string} */ - format: string; + format: 'WAVE'; /** * The data of the 'fmt' chunk. * @type {!Object} */ - fmt: object; + fmt: WaveFileFmtChunk; /** * The data of the 'fact' chunk. * @type {!Object} @@ -55,6 +53,16 @@ declare module wavefile { * @type {!Object} */ bext: object; + /** + * The data of the 'mext' chunk. + * @type {!Object} + */ + mext: object; + /** + * The data of the 'cart' chunk. + * @type {!Object} + */ + cart: object; /** * The data of the 'iXML' chunk. * @type {!Object} @@ -70,7 +78,7 @@ declare module wavefile { * The data of the 'data' chunk. * @type {!Object} */ - data: object; + data: WaveFileDataChunk; /** * The data of the 'LIST' chunks. * Each item in this list look like this: @@ -93,6 +101,12 @@ declare module wavefile { * @type {!Object} */ _PMX: object; + /** + * Whether to apply a pad byte or not + * Defaults to 'true' + * @type {boolean} + */ + padBytes: boolean; /** * @param {Uint8Array=} [wavBuffer=null] A wave file buffer. @@ -109,7 +123,7 @@ declare module wavefile { * @param {Function=} [OutputObject=Float64Array] The sample container. * @return {!(Array|TypedArray)} the samples. */ - getSamples(interleaved?:boolean, OutputObject?: Function): Float64Array; + getSamples(interleaved?: boolean, OutputObject?: Function): Float64Array; /** * Return the sample at a given index. @@ -145,8 +159,21 @@ declare module wavefile { numChannels: number, sampleRate: number, bitDepthCode: string, - samples: Array|Array>|ArrayLike|Array>, - options?: object): void; + samples: + | Array + | Array> + | ArrayLike + | Array>, + options?: object + ): void; + + /** + * Set up the WaveFileCreator object from an mpeg buffer and/or optional info. + * @param {!Uint8Array} mpegBuffer The buffer. + * @param {Object=} info Optional Mpeg info such as version, layer, etc. + * @throws {Error} If the mpeg file cannot be parsed + */ + fromMpeg(mpegBuffer: Uint8Array, info?: object): void; /** * Set up the WaveFileParser object from a byte buffer. @@ -157,7 +184,7 @@ declare module wavefile { * @throws {Error} If no 'fmt ' chunk is found. * @throws {Error} If no 'data' chunk is found. */ - fromBuffer(bytes: Uint8Array, samples?:boolean): void; + fromBuffer(bytes: Uint8Array, samples?: boolean): void; /** * Return a byte buffer representig the WaveFileParser object as a .wav file. @@ -223,7 +250,7 @@ declare module wavefile { * @param {number} sampleRate The target sample rate. * @param {Object=} options The extra configuration, if needed. */ - toSampleRate(samples: number, options?:object): void; + toSampleRate(samples: number, options?: object): void; /** * Encode a 16-bit wave file as 4-bit IMA ADPCM. @@ -277,7 +304,7 @@ declare module wavefile { * @param {string} tag The tag name. * @return {?string} The value if the tag is found, null otherwise. */ - getTag(tag: string): string|null; + getTag(tag: string): string | null; /** * Return a Object with the RIFF tags in the file. @@ -363,4 +390,43 @@ declare module wavefile { */ get_PMX(): string; } + + type WaveFileDataChunk = { + /** @type {string} */ + chunkId: 'data'; + /** @type {number} */ + chunkSize: number; + /** @type {!Uint8Array} */ + samples: Uint8Array; + }; + + type WaveFileFmtChunk = { + /** @type {string} */ + chunkId: 'fmt '; + /** @type {number} */ + chunkSize: number; + /** @type {number} */ + audioFormat: number; + /** @type {number} */ + numChannels: number; + /** @type {number} */ + sampleRate: number; + /** @type {number} */ + byteRate: number; + /** @type {number} */ + blockAlign: number; + /** @type {number} */ + bitsPerSample: number; + /** @type {number} */ + cbSize: number; + /** @type {number} */ + validBitsPerSample: number; + /** @type {number} */ + dwChannelMask: number; + /** + * 4 32-bit values representing a 128-bit ID + * @type {!Array} + */ + subformat: readonly [number,number,number,number]; + }; } diff --git a/lib/mpeg-reader.js b/lib/mpeg-reader.js new file mode 100644 index 0000000..1401572 --- /dev/null +++ b/lib/mpeg-reader.js @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2020 Andrew Kuklewicz + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/** + * @fileoverview The MpegReader class. + * @see https://github.com/rochars/wavefile + */ + +import { unpackString, unpack } from "./parsers/binary"; + +/** + * A class to perform low-level reading of mpeg audio files. + */ +export class MpegReader { + constructor(mpegBuffer) { + /** + * @type {number} + * @protected + */ + this.head = 0; + + // Header values + /** + * @type {number} + */ + this.version = 0; + /** + * @type {number} + */ + this.layer = 0; + /** + * @type {boolean} + */ + this.errorProtection = false; + /** + * @type {number} + */ + this.bitRate = 0; + /** + * @type {number} + */ + this.sampleRate = 0; + /** + * @type {boolean} + */ + this.padding = false; + /** + * @type {boolean} + */ + this.privateBit = false; + /** + * @type {string} + */ + this.channelMode = ""; + /** + * @type {number} + */ + this.modeExtension = 0; + /** + * @type {boolean} + */ + this.copyright = false; + /** + * @type {boolean} + */ + this.original = false; + /** + * @type {number} + */ + this.emphasis = 0; + + // Calculated + /** + * @type {number} + */ + this.numChannels = 0; + /** + * @type {number} + */ + this.id3v2Offset = 0; + /** + * @type {number} + */ + this.samplesPerFrame = 0; + /** + * @type {number} + */ + this.frameSize = 0; + /** + * @type {number} + */ + this.sampleLength = 0; + /** + * @type {number} + */ + this.durationEstimate = 0.0; + /** + * @type {boolean} + */ + this.homogeneous = true; + /** + * @type {boolean} + */ + this.freeForm = false; + + this.uInt32BE = { bits: 32, be: true }; + + // Constants & Lookups + /** MPEG Versions + 00: MPEG Version 2.5 (unofficial) + 01: (reserved) + 10: MPEG Version 2 (ISO/IEC 13818-3) + 11: MPEG Version 1 (ISO/IEC 11172-3) + */ + this.VERSIONS = [2.5, null, 2, 1]; + + /** MPEG Layers + 00: (reserved) + 01: Layer III (i.e. mp3 files) + 10: Layer II (i.e. mp2 files) + 11: Layer I + */ + this.LAYERS = [0, 3, 2, 1]; + + /** + * Sample rate table: + * the sample rate value is calculated based on the mpeg version + */ + this.CHANNEL_MODES = ["stereo", "joint-stereo", "dual-mono", "mono"]; + + /** + * Samples per frame table: + * the number of samples per frame is calculated based on the mpeg version and layer + */ + this.SAMPLES_PER_FRAME = { + 1: { 0: 0, 1: 384, 2: 1152, 3: 1152 }, + 2: { 0: 0, 1: 384, 2: 1152, 3: 576 } + }; + + /** + * Bitrates table: + * the bitRate value is calculated based on the mpeg version and layer + */ + // prettier-ignore + this.BITRATES = { + 1: { + 1: [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0], + 2: [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0], + 3: [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + 2: { + 1: [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0], + 2: [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0], + 3: [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } + }; + + /** + * Sample rate table: + * the sample rate value is calculated based on the mpeg version + */ + this.SAMPLE_RATES = { + 1: [44100, 48000, 32000, 0], + 2: [22050, 24000, 16000, 0] + }; + + if (mpegBuffer) { + this.fromBuffer(mpegBuffer); + } + } + + /** + * Set up the MpegReader object from an mpeg byte buffer. + * @param {!Uint8Array} mpegBuffer The buffer. + * @throws {Error} If format is not mpeg. + */ + fromBuffer(mpegBuffer) { + this.findFirstFrame_(mpegBuffer); + this.parseFrame_(mpegBuffer); + } + + /** + * Find the first mpeg frame, skipping id3 and other garbage, looking for 11111111 111 + * @param {!Uint8Array} buffer The buffer. + * @throws {Error} If format is not mpeg. + */ + findFirstFrame_(buffer) { + this.id3v2Offset = this.skipId3_(buffer); + this.head += this.id3v2Offset; + + // b[0] should be 11111111 == 255 (0xff) + // b[1] should be 111????? > 128 + 64 + 32 > 224 (0xe0) + while (this.head + 4 < buffer.length) { + let b = [buffer[this.head], buffer[this.head + 1]]; + if (b[0] == 0xff && (b[1] & 0xe0) == 0xe0) { + break; + } else { + this.head += b[1] == 0xff ? 1 : 2; + } + } + return this.head; + } + + /** + * Skip any id3 or other data at the start of the file + * @param {!Uint8Array} buffer The buffer. + * @throws {Error} If format is not mpeg. + */ + skipId3_(buffer) { + let offset = 0; + // http://id3.org/d3v2.3.0 + if (unpackString(buffer, 0, 3) == "ID3") { + // Decode bytes 6-9 as a 32-bit "synchsafe int" (refer to any ID3v2 spec). + let tagSizeBuffer = buffer.subarray(6, 10); + let tagSize = unsynchsafe(unpack(tagSizeBuffer, this.uInt32BE, 0)); + + offset = 10 + tagSize; + // If the 0x10 bit of byte 5 is set, let OFFSET = OFFSET + 10 (for the footer). + if (isBitSet(buffer[5], 2)) { + offset += 10; + } + } + return offset; + } + + /** + * Parse the header and calculate derived values + * @param {!Uint8Array} buffer The buffer. + * @throws {Error} If format is not mpeg. + */ + parseFrame_(buffer) { + this.parseHeader_(buffer); + this.numChannels = this.channelMode == "mono" ? 1 : 2; + this.samplesPerFrame = this.SAMPLES_PER_FRAME[this.version][this.layer]; + this.frameSize = this.frameSizeCalc_(); + this.sampleLength = this.sampleLengthCalc_(buffer); + this.durationEstimate = this.durationEstimateCalc_(buffer); + } + + durationEstimateCalc_(buffer) { + return (buffer.length - this.id3v2Offset) / ((this.bitRate * 1000) / 8); + } + + sampleLengthCalc_(buffer) { + let fs = Math.floor((buffer.length - this.id3v2Offset) / this.frameSize); + return fs * this.samplesPerFrame; + } + + frameSizeCalc_() { + return this.mpegFrameSizeCalc( + this.samplesPerFrame, + this.layer, + this.bitRate, + this.sampleRate, + this.padding + ); + } + + // version from ruby code + mpegFrameSizeCalc(samplesPerFrame, layer, bitRate, sampleRate, padding) { + var byteRate = (bitRate * 1000) / 8; + var pad = (padding ? 1 : 0) * (layer == 1 ? 4 : 1); + return ((samplesPerFrame * byteRate) / sampleRate + pad) | 0; + } + + // http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm + parseHeader_(buffer) { + let h = []; + for (let i = 0; i < 4; i++) { + h[i] = this.readUInt8(buffer); + } + + // Validate the frane sync + if (h[0] !== 0xff || (h[1] & 0xe0) !== 0xe0) { + throw new Error(`Invalid frame header: [255, 224] != [${h[0]}, ${h[1]}]`); + } + + // Byte 0: `AAAAAAAA` + // `AAAAAAAA` | 8 | (32-24) | Frame sync, part I (all bits set) + + // Byte 1: `AAABBCCD` + // `AAA.....` | 3 | (23,21) | Frame sync part II (3 bits set) + // `...BB...` | 2 | (20,19) | MPEG Audio version ID (11 -> MPEG Version 1 (ISO/IEC 11172-3)) + // `.....CC.` | 2 | (18,17) | Layer description + // `.......D` | 1 | (16) | Protection bit | 0 - Protected by CRC, 1 - Not protected + this.version = this.VERSIONS[(h[1] >> 3) & 0x03]; + this.layer = this.LAYERS[(h[1] >> 1) & 0x03]; + this.errorProtection = !isBitSet(h[1], 1); + + // Byte 2: `EEEEFFGH` + // `EEEE....` | 4 | (15,12) | Bitrate index + // `....FF..` | 2 | (11,10) | Sampling rate frequency index (values are in Hz) + // `......G.` | 1 | (9) | Padding bit, 0 - frame is not padded, 1 - frame is padded with one extra slot + // `.......H` | 1 | (8) | Private bit. This is informative + this.bitRate = this.BITRATES[this.version][this.layer][(h[2] >> 4) & 0x0f]; + this.sampleRate = this.SAMPLE_RATES[this.version][(h[2] >> 2) & 0x03]; + this.padding = isBitSet(h[2], 2); + this.privateBit = isBitSet(h[2], 1); + + // Byte 3: `IIJJKLMM` + // `II......` | 2 | (7,6) | Channel Mode + // `..JJ....` | 2 | (5,4) | Mode extension (Only if Joint stereo) + // `....K...` | 1 | (3) | Copyright + // `.....L..` | 1 | (2) | Original + // `......MM` | 2 | (1,0) | Emphasis + this.channelMode = this.CHANNEL_MODES[(h[3] >> 6) & 0x03]; + this.modeExtension = (h[3] >> 6) & 0x03; + this.copyright = isBitSet(h[3], 4); + this.original = isBitSet(h[3], 3); + this.emphasis = h[3] & 0x03; + } + + /** + * Read a number from a chunk. + * @param {!Uint8Array} bytes The chunk bytes. + * @return {number} The number. + * @protected + */ + readUInt8(bytes) { + let value = bytes[this.head]; + this.head += 1; + return value; + } +} + +/** + * Decodes a sync-safe integer + * @param {number} i sync-safe integer + * @return {number} un-sync-safe integer + */ +export function unsynchsafe(i) { + let mask = 0x7f000000; + let out = 0; + + while (mask) { + out >>= 1; + out |= i & mask; + mask >>= 8; + } + return out; +} + +/** + * Returns + * @param {number} n integer to look for a bit set + * @param {number} b position of the bit to check + * @return {boolean} True if the bit is set + */ +function isBitSet(n, b) { + return n & (1 << (b - 1)) ? true : false; +} diff --git a/lib/riff-file.js b/lib/riff-file.js index ca7d4d1..60de2d2 100644 --- a/lib/riff-file.js +++ b/lib/riff-file.js @@ -72,6 +72,12 @@ export class RIFFFile { * @protected */ this.supported_containers = ['RIFF', 'RIFX']; + /** + * Whether to apply a pad byte or not + * Defaults to 'true' + * @type {boolean} + */ + this.padBytes = true; } /** @@ -167,7 +173,9 @@ export class RIFFFile { while(i <= buffer.length - 8) { chunks.push(this.getSubChunkIndex_(buffer, i)); i += 8 + chunks[chunks.length - 1].chunkSize; - i = i % 2 ? i + 1 : i; + if (this.padBytes) { + i = i % 2 ? i + 1 : i; + } } return chunks; } @@ -191,8 +199,10 @@ export class RIFFFile { chunk.subChunks = this.getSubChunksIndex_(buffer); } else { /** @type {number} */ - let realChunkSize = chunk.chunkSize % 2 ? - chunk.chunkSize + 1 : chunk.chunkSize; + let realChunkSize = chunk.chunkSize; + if (this.padBytes) { + realChunkSize = realChunkSize % 2 ? realChunkSize + 1 : realChunkSize; + } this.head = index + 8 + realChunkSize; chunk.chunkData = { start: index + 8, diff --git a/lib/wavefile-creator.js b/lib/wavefile-creator.js index 9b77ee3..d44f3b1 100644 --- a/lib/wavefile-creator.js +++ b/lib/wavefile-creator.js @@ -27,31 +27,30 @@ * @see https://github.com/rochars/wavefile */ -import { WaveFileParser } from './wavefile-parser'; -import { interleave, deInterleave } from './parsers/interleave'; -import { validateNumChannels } from './validators/validate-num-channels'; -import { validateSampleRate } from './validators/validate-sample-rate'; -import { packArrayTo, unpackArrayTo, packTo, unpack } from './parsers/binary'; - +import { WaveFileParser } from "./wavefile-parser"; +import { interleave, deInterleave } from "./parsers/interleave"; +import { validateNumChannels } from "./validators/validate-num-channels"; +import { validateSampleRate } from "./validators/validate-sample-rate"; +import { packArrayTo, unpackArrayTo, packTo, unpack } from "./parsers/binary"; +import { MpegReader } from "./mpeg-reader"; /** * A class to read, write and create wav files. * @extends WaveFileParser * @ignore */ export class WaveFileCreator extends WaveFileParser { - constructor() { super(); /** * The bit depth code according to the samples. * @type {string} */ - this.bitDepth = '0'; + this.bitDepth = "0"; /** * @type {!{bits: number, be: boolean}} * @protected */ - this.dataType = {bits: 0, be: false}; + this.dataType = { bits: 0, be: false }; /** * Audio formats. * Formats not listed here should be set to 65534, @@ -60,15 +59,16 @@ export class WaveFileCreator extends WaveFileParser { * @protected */ this.WAV_AUDIO_FORMATS = { - '4': 17, - '8': 1, - '8a': 6, - '8m': 7, - '16': 1, - '24': 1, - '32': 1, - '32f': 3, - '64': 3 + "4": 17, + "8": 1, + "8a": 6, + "8m": 7, + "16": 1, + "24": 1, + "32": 1, + "32f": 3, + "64": 3, + "65535": 80 // mpeg == 80 }; } @@ -93,6 +93,182 @@ export class WaveFileCreator extends WaveFileParser { this.newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options); } + /** + * Set up the WaveFileCreator object from an mpeg buffer and metadata info. + * @param {!Uint8Array} mpegBuffer The buffer. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * Required mpeg info: + * info.version + * info.layer + * info.errorProtection + * info.bitRate + * info.sampleRate + * info.padding + * info.privateBit + * info.channelMode + * info.modeExtension + * info.copyright + * info.original + * info.emphasis + * info.numChannels + * info.frameSize + * info.sampleLength + * @throws {Error} If any argument does not meet the criteria. + */ + fromMpeg(mpegBuffer, info = null) { + this.clearHeaders(); + + if (info == null) { + info = new MpegReader(mpegBuffer); + } + + let codingHistory = this.mpegCodingHistory_(info); + + // riff(4) + fmt(40+8) + mext(12+8) + bxt(602+8+codingHistory.length) + fact(4+8) + buffer.length + // 4 + 48 + 20 + 610 + 12 + codingHistory.length + buffer.length + this.container = "RIFF"; + this.chunkSize = 694 + codingHistory.length + mpegBuffer.length; + this.format = "WAVE"; + this.bitDepth = "65535"; + + this.fmt.chunkId = "fmt "; + this.fmt.chunkSize = 40; + this.fmt.audioFormat = 80; + this.fmt.numChannels = info.numChannels; + this.fmt.sampleRate = info.sampleRate; + this.fmt.byteRate = (info.bitRate / 8) * 1000; + this.fmt.blockAlign = info.frameSize; + this.fmt.bitsPerSample = 65535; + this.fmt.cbSize = 22; + this.fmt.headLayer = Math.pow(2, info.layer - 1); + this.fmt.headBitRate = info.bitRate * 1000; + this.fmt.headMode = this.mpegHeadMode_(info); + this.fmt.headModeExt = this.mpegHeadModeExt_(info); + this.fmt.headEmphasis = info.emphasis + 1; + this.fmt.headFlags = this.mpegHeadFlags_(info); + this.fmt.ptsLow = 0; + this.fmt.ptsHigh = 0; + + this.mext.chunkId = "mext"; + this.mext.chunkSize = 12; + this.mext.soundInformation = this.mpegSoundInformation_(info); + this.mext.frameSize = info.frameSize; + this.mext.ancillaryDataLength = 0; + this.mext.ancillaryDataDef = 0; + this.mext.reserved = ""; + + this.bext.chunkId = "bext"; + this.bext.chunkSize = 602 + codingHistory.length; + this.bext.timeReference = [0, 0]; + this.bext.version = 1; + this.bext.codingHistory = codingHistory; + + this.fact.chunkId = "fact"; + this.fact.chunkSize = 4; + this.fact.dwSampleLength = info.sampleLength; + + this.data.chunkId = "data"; + this.data.samples = mpegBuffer; + this.data.chunkSize = this.data.samples.length; + } + + mpegSoundInformation_(info) { + let soundInformation = 0; + if (info.homogeneous) { + soundInformation += 1; + } + if (!info.padding) { + soundInformation += 2; + } + if ( + (info.sampleRate == 44100 || info.sampleRate == 22050) && + !info.padding + ) { + soundInformation += 4; + } + if (info.freeForm) { + soundInformation += 8; + } + return soundInformation; + } + + /** + * Returns the mode value based on the channel mode of the mpeg file + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ + // prettier-ignore + mpegHeadMode_(info) { + return { + "stereo": 1, + "joint-stereo": 2, + "dual-mono": 4, + "mono": 8 + }[info.channelMode]; + } + + /** + * Contains extra parameters for joint–stereo coding; not used for other modes. + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + */ + mpegHeadModeExt_(info) { + if (info.channelMode == "joint-stereo") { + return Math.pow(2, info.modeExtension); + } else { + return 0; + } + } + + /** + * Follows EBU established standards for CodingHistory for MPEG + * EBU Technical Recommendation R98-1999, https://tech.ebu.ch/docs/r/r098.pdf + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + **/ + mpegCodingHistory_(info) { + return ( + "A=MPEG" + + info.version + + "L" + + info.layer + + ",F=" + + info.sampleRate + + ",B=" + + info.bitRate + + ",M=" + + info.channelMode + + ",T=wavefile\r\n\0\0" + ); + } + + /** + * Follows EBU standards for `fmt` chunk `fwHeadFlags` for MPEG in BWF + * EBU Tech. 3285–E – Supplement 1, 1997 + * https://tech.ebu.ch/docs/tech/tech3285s1.pdf + * @param {Object=} info Mpeg info such as version, layer, bitRate, etc. + * @throws {Error} If any argument does not meet the criteria. + **/ + mpegHeadFlags_(info) { + let flags = 0; + if (info.privateBit) { + flags += 1; + } + if (info.copyright) { + flags += 2; + } + if (info.original) { + flags += 4; + } + if (info.errorProtection) { + flags += 8; + } + if (info.version > 0) { + flags += 16; + } + return flags; + } + /** * Set up the WaveFileParser object from a byte buffer. * @param {!Uint8Array} wavBuffer The buffer. @@ -102,7 +278,7 @@ export class WaveFileCreator extends WaveFileParser { * @throws {Error} If no 'fmt ' chunk is found. * @throws {Error} If no 'data' chunk is found. */ - fromBuffer(wavBuffer, samples=true) { + fromBuffer(wavBuffer, samples = true) { super.fromBuffer(wavBuffer, samples); this.bitDepthFromFmt_(); this.updateDataType_(); @@ -128,17 +304,23 @@ export class WaveFileCreator extends WaveFileParser { * @param {Function=} [OutputObject=Float64Array] The sample container. * @return {!(Array|TypedArray)} the samples. */ - getSamples(interleaved=false, OutputObject=Float64Array) { + getSamples(interleaved = false, OutputObject = Float64Array) { /** * A Float64Array created with a size to match the * the length of the samples. * @type {!(Array|TypedArray)} */ let samples = new OutputObject( - this.data.samples.length / (this.dataType.bits / 8)); + this.data.samples.length / (this.dataType.bits / 8) + ); // Unpack all the samples - unpackArrayTo(this.data.samples, this.dataType, samples, - 0, this.data.samples.length); + unpackArrayTo( + this.data.samples, + this.dataType, + samples, + 0, + this.data.samples.length + ); if (!interleaved && this.fmt.numChannels > 1) { return deInterleave(samples, this.fmt.numChannels, OutputObject); } @@ -154,11 +336,12 @@ export class WaveFileCreator extends WaveFileParser { getSample(index) { index = index * (this.dataType.bits / 8); if (index + this.dataType.bits / 8 > this.data.samples.length) { - throw new Error('Range error'); + throw new Error("Range error"); } return unpack( this.data.samples.slice(index, index + this.dataType.bits / 8), - this.dataType); + this.dataType + ); } /** @@ -170,7 +353,7 @@ export class WaveFileCreator extends WaveFileParser { setSample(index, sample) { index = index * (this.dataType.bits / 8); if (index + this.dataType.bits / 8 > this.data.samples.length) { - throw new Error('Range error'); + throw new Error("Range error"); } packTo(sample, this.dataType, this.data.samples, index, true); } @@ -189,11 +372,11 @@ export class WaveFileCreator extends WaveFileParser { * @throws {TypeError} If the value is not a string. */ setiXML(iXMLValue) { - if (typeof iXMLValue !== 'string') { - throw new TypeError('iXML value must be a string.'); + if (typeof iXMLValue !== "string") { + throw new TypeError("iXML value must be a string."); } this.iXML.value = iXMLValue; - this.iXML.chunkId = 'iXML'; + this.iXML.chunkId = "iXML"; } /** @@ -210,11 +393,11 @@ export class WaveFileCreator extends WaveFileParser { * @throws {TypeError} If the value is not a string. */ set_PMX(_PMXValue) { - if (typeof _PMXValue !== 'string') { - throw new TypeError('_PMX value must be a string.'); + if (typeof _PMXValue !== "string") { + throw new TypeError("_PMX value must be a string."); } this._PMX.value = _PMXValue; - this._PMX.chunkId = '_PMX'; + this._PMX.chunkId = "_PMX"; } /** @@ -232,7 +415,7 @@ export class WaveFileCreator extends WaveFileParser { */ newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options) { if (!options.container) { - options.container = 'RIFF'; + options.container = "RIFF"; } this.container = options.container; this.bitDepth = bitDepthCode; @@ -243,9 +426,14 @@ export class WaveFileCreator extends WaveFileParser { this.data.samples = new Uint8Array(samples.length * numBytes); packArrayTo(samples, this.dataType, this.data.samples, 0, true); this.makeWavHeader_( - bitDepthCode, numChannels, sampleRate, - numBytes, this.data.samples.length, options); - this.data.chunkId = 'data'; + bitDepthCode, + numChannels, + sampleRate, + numBytes, + this.data.samples.length, + options + ); + this.data.chunkId = "data"; this.data.chunkSize = this.data.samples.length; this.validateWavHeader_(); } @@ -261,23 +449,52 @@ export class WaveFileCreator extends WaveFileParser { * @private */ makeWavHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { - if (bitDepthCode == '4') { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { + if (bitDepthCode == "4") { this.createADPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); - - } else if (bitDepthCode == '8a' || bitDepthCode == '8m') { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); + } else if (bitDepthCode == "8a" || bitDepthCode == "8m") { this.createALawMulawHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); - - } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 || - numChannels > 2) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); + } else if ( + Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 || + numChannels > 2 + ) { this.createExtensibleHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); - + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); } else { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); } } @@ -292,18 +509,24 @@ export class WaveFileCreator extends WaveFileParser { * @private */ createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.container = options.container; this.chunkSize = 36 + samplesLength; - this.format = 'WAVE'; + this.format = "WAVE"; this.bitDepth = bitDepthCode; this.fmt = { - chunkId: 'fmt ', + chunkId: "fmt ", chunkSize: 16, audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534, numChannels: numChannels, sampleRate: sampleRate, - byteRate: (numChannels * numBytes) * sampleRate, + byteRate: numChannels * numBytes * sampleRate, blockAlign: numChannels * numBytes, bitsPerSample: parseInt(bitDepthCode, 10), cbSize: 0, @@ -324,9 +547,21 @@ export class WaveFileCreator extends WaveFileParser { * @private */ createADPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); this.chunkSize = 40 + samplesLength; this.fmt.chunkSize = 20; this.fmt.byteRate = 4055; @@ -335,7 +570,7 @@ export class WaveFileCreator extends WaveFileParser { this.fmt.cbSize = 2; this.fmt.validBitsPerSample = 505; this.fact = { - chunkId: 'fact', + chunkId: "fact", chunkSize: 4, dwSampleLength: samplesLength * 2 }; @@ -352,9 +587,21 @@ export class WaveFileCreator extends WaveFileParser { * @private */ createExtensibleHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); this.chunkSize = 36 + 24 + samplesLength; this.fmt.chunkSize = 40; this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1; @@ -377,15 +624,27 @@ export class WaveFileCreator extends WaveFileParser { * @private */ createALawMulawHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) { + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ) { this.createPCMHeader_( - bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options); + bitDepthCode, + numChannels, + sampleRate, + numBytes, + samplesLength, + options + ); this.chunkSize = 40 + samplesLength; this.fmt.chunkSize = 20; this.fmt.cbSize = 2; this.fmt.validBitsPerSample = 8; this.fact = { - chunkId: 'fact', + chunkId: "fact", chunkSize: 4, dwSampleLength: samplesLength }; @@ -397,11 +656,13 @@ export class WaveFileCreator extends WaveFileParser { */ bitDepthFromFmt_() { if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) { - this.bitDepth = '32f'; + this.bitDepth = "32f"; } else if (this.fmt.audioFormat === 6) { - this.bitDepth = '8a'; + this.bitDepth = "8a"; } else if (this.fmt.audioFormat === 7) { - this.bitDepth = '8m'; + this.bitDepth = "8m"; + } else if (this.fmt.audioFormat === 80) { + this.bitDepth = "65535"; } else { this.bitDepth = this.fmt.bitsPerSample.toString(); } @@ -415,11 +676,10 @@ export class WaveFileCreator extends WaveFileParser { */ validateBitDepth_() { if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) { - if (parseInt(this.bitDepth, 10) > 8 && - parseInt(this.bitDepth, 10) < 54) { + if (parseInt(this.bitDepth, 10) > 8 && parseInt(this.bitDepth, 10) < 54) { return true; } - throw new Error('Invalid bit depth.'); + throw new Error("Invalid bit depth."); } return true; } @@ -431,11 +691,11 @@ export class WaveFileCreator extends WaveFileParser { updateDataType_() { this.dataType = { bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1, - fp: this.bitDepth == '32f' || this.bitDepth == '64', - signed: this.bitDepth != '8', - be: this.container == 'RIFX' + fp: this.bitDepth == "32f" || this.bitDepth == "64", + signed: this.bitDepth != "8", + be: this.container == "RIFX" }; - if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) { + if (["4", "8a", "8m"].indexOf(this.bitDepth) > -1) { this.dataType.bits = 8; this.dataType.signed = false; } @@ -452,11 +712,16 @@ export class WaveFileCreator extends WaveFileParser { validateWavHeader_() { this.validateBitDepth_(); if (!validateNumChannels(this.fmt.numChannels, this.fmt.bitsPerSample)) { - throw new Error('Invalid number of channels.'); + throw new Error("Invalid number of channels."); } - if (!validateSampleRate( - this.fmt.numChannels, this.fmt.bitsPerSample, this.fmt.sampleRate)) { - throw new Error('Invalid sample rate.'); + if ( + !validateSampleRate( + this.fmt.numChannels, + this.fmt.bitsPerSample, + this.fmt.sampleRate + ) + ) { + throw new Error("Invalid sample rate."); } } } @@ -473,18 +738,18 @@ function dwChannelMask_(numChannels) { // mono = FC if (numChannels === 1) { mask = 0x4; - // stereo = FL, FR + // stereo = FL, FR } else if (numChannels === 2) { mask = 0x3; - // quad = FL, FR, BL, BR + // quad = FL, FR, BL, BR } else if (numChannels === 4) { mask = 0x33; - // 5.1 = FL, FR, FC, LF, BL, BR + // 5.1 = FL, FR, FC, LF, BL, BR } else if (numChannels === 6) { - mask = 0x3F; - // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR + mask = 0x3f; + // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR } else if (numChannels === 8) { - mask = 0x63F; + mask = 0x63f; } return mask; } diff --git a/lib/wavefile-parser.js b/lib/wavefile-parser.js index f88ae18..ec3ecbb 100644 --- a/lib/wavefile-parser.js +++ b/lib/wavefile-parser.js @@ -27,29 +27,30 @@ * @see https://github.com/rochars/wavefile */ -import { WaveFileReader } from './wavefile-reader'; -import { writeString } from './parsers/write-string'; -import { packTo, packStringTo, packString, pack } from './parsers/binary'; +import { WaveFileReader } from "./wavefile-reader"; +import { writeString } from "./parsers/write-string"; +import { packTo, packStringTo, packString, pack } from "./parsers/binary"; /** * A class to read and write wav files. * @extends WaveFileReader */ export class WaveFileParser extends WaveFileReader { - /** * Return a byte buffer representig the WaveFileParser object as a .wav file. * The return value of this method can be written straight to disk. * @return {!Uint8Array} A wav file. */ toBuffer() { - this.uInt16.be = this.container === 'RIFX'; + this.uInt16.be = this.container === "RIFX"; this.uInt32.be = this.uInt16.be; /** @type {!Array>} */ let fileBody = [ this.getJunkBytes_(), this.getDs64Bytes_(), this.getBextBytes_(), + this.getMextBytes_(), + this.getCartBytes_(), this.getiXMLBytes_(), this.getFmtBytes_(), this.getFactBytes_(), @@ -63,7 +64,7 @@ export class WaveFileParser extends WaveFileReader { ]; /** @type {number} */ let fileBodyLength = 0; - for (let i=0; i} */ + let bytes = []; + if (this.mext.chunkId) { + this.mext.chunkSize = 12; + bytes = bytes.concat( + packString(this.mext.chunkId), + pack(this.mext.chunkSize, this.uInt32), + pack(this.mext.soundInformation, this.uInt16), + pack(this.mext.frameSize, this.uInt16), + pack(this.mext.ancillaryDataLength, this.uInt16), + pack(this.mext.ancillaryDataDef, this.uInt16), + writeString(this.mext.reserved, 4) + ); + } + return bytes; + } + /** * Make sure a 'bext' chunk is created if BWF data was created in a file. * @private @@ -122,17 +145,79 @@ export class WaveFileParser extends WaveFileReader { enforceBext_() { for (let prop in this.bext) { if (this.bext.hasOwnProperty(prop)) { - if (this.bext[prop] && prop != 'timeReference') { - this.bext.chunkId = 'bext'; + if (this.bext[prop] && prop != "timeReference") { + this.bext.chunkId = "bext"; break; } } } if (this.bext.timeReference[0] || this.bext.timeReference[1]) { - this.bext.chunkId = 'bext'; + this.bext.chunkId = "bext"; } } + /** + * Return the bytes of the 'cart' chunk. + * @private + */ + getCartBytes_() { + /** @type {!Array} */ + let bytes = []; + let postTimerBytes = this.getPostTimerBytes_(); + if (this.cart.chunkId) { + this.cart.chunkSize = 2048 + this.cart.tagText.length; + bytes = bytes.concat( + packString(this.cart.chunkId), + pack(this.cart.chunkSize, this.uInt32), + writeString(this.cart.version, 4), + writeString(this.cart.title, 64), + writeString(this.cart.artist, 64), + writeString(this.cart.cutId, 64), + writeString(this.cart.clientId, 64), + writeString(this.cart.category, 64), + writeString(this.cart.classification, 64), + writeString(this.cart.outCue, 64), + writeString(this.cart.startDate, 10), + writeString(this.cart.startTime, 8), + writeString(this.cart.endDate, 10), + writeString(this.cart.endTime, 8), + writeString(this.cart.producerAppId, 64), + writeString(this.cart.producerAppVersion, 64), + writeString(this.cart.userDef, 64), + pack(this.cart.levelReference, this.uInt32), + postTimerBytes, + writeString(this.cart.reserved, 276), + writeString(this.cart.url, 1024), + writeString(this.cart.tagText, this.cart.tagText.length) + ); + } + this.enforceByteLen_(bytes); + return bytes; + } + + /** + * Return the bytes of the 'cart' postTimers + * @return {!Array} The 'cart' postTimers as an array of bytes. + * @private + */ + getPostTimerBytes_() { + /** @type {!Array} */ + let postTimers = []; + for (let i = 0; i < 8; i++) { + let usage = ""; + let value = 4294967295; + if (i < this.cart.postTimer.length) { + usage = this.cart.postTimer[i].usage; + value = this.cart.postTimer[i].value; + } + postTimers = postTimers.concat( + writeString(usage, 4), + pack(value, this.uInt32) + ); + } + return postTimers; + } + /** * Return the bytes of the 'iXML' chunk. * @return {!Array} The 'iXML' chunk bytes. @@ -148,7 +233,8 @@ export class WaveFileParser extends WaveFileReader { bytes = bytes.concat( packString(this.iXML.chunkId), pack(this.iXML.chunkSize, this.uInt32), - iXMLPackedValue); + iXMLPackedValue + ); } this.enforceByteLen_(bytes); return bytes; @@ -172,7 +258,8 @@ export class WaveFileParser extends WaveFileReader { pack(this.ds64.dataSizeLow, this.uInt32), pack(this.ds64.originationTime, this.uInt32), pack(this.ds64.sampleCountHigh, this.uInt32), - pack(this.ds64.sampleCountLow, this.uInt32)); + pack(this.ds64.sampleCountLow, this.uInt32) + ); } //if (this.ds64.tableLength) { // ds64Bytes = ds64Bytes.concat( @@ -198,7 +285,8 @@ export class WaveFileParser extends WaveFileReader { packString(this.cue.chunkId), pack(cuePointsBytes.length + 4, this.uInt32), // chunkSize pack(this.cue.dwCuePoints, this.uInt32), - cuePointsBytes); + cuePointsBytes + ); } this.enforceByteLen_(bytes); return bytes; @@ -212,14 +300,15 @@ export class WaveFileParser extends WaveFileReader { getCuePointsBytes_() { /** @type {!Array} */ let points = []; - for (let i=0; i} */ let loops = []; - for (let i=0; i} */ - let bytes = fmtBytes.concat( + let bytes = fmtBytes.concat( packString(this.fmt.chunkId), pack(this.fmt.chunkSize, this.uInt32), pack(this.fmt.audioFormat, this.uInt16), @@ -311,7 +403,8 @@ export class WaveFileParser extends WaveFileReader { pack(this.fmt.byteRate, this.uInt32), pack(this.fmt.blockAlign, this.uInt16), pack(this.fmt.bitsPerSample, this.uInt16), - this.getFmtExtensionBytes_()); + this.getFmtExtensionBytes_() + ); this.enforceByteLen_(bytes); return bytes; } @@ -327,23 +420,36 @@ export class WaveFileParser extends WaveFileReader { /** @type {!Array} */ let extension = []; if (this.fmt.chunkSize > 16) { - extension = extension.concat( - pack(this.fmt.cbSize, this.uInt16)); + extension = extension.concat(pack(this.fmt.cbSize, this.uInt16)); } - if (this.fmt.chunkSize > 18) { + if (this.fmt.audioFormat == 80 && this.fmt.chunkSize == 40) { extension = extension.concat( - pack(this.fmt.validBitsPerSample, this.uInt16)); - } - if (this.fmt.chunkSize > 20) { - extension = extension.concat( - pack(this.fmt.dwChannelMask, this.uInt32)); - } - if (this.fmt.chunkSize > 24) { - extension = extension.concat( - pack(this.fmt.subformat[0], this.uInt32), - pack(this.fmt.subformat[1], this.uInt32), - pack(this.fmt.subformat[2], this.uInt32), - pack(this.fmt.subformat[3], this.uInt32)); + pack(this.fmt.headLayer, this.uInt16), + pack(this.fmt.headBitRate, this.uInt32), + pack(this.fmt.headMode, this.uInt16), + pack(this.fmt.headModeExt, this.uInt16), + pack(this.fmt.headEmphasis, this.uInt16), + pack(this.fmt.headFlags, this.uInt16), + pack(this.fmt.ptsLow, this.uInt32), + pack(this.fmt.ptsHigh, this.uInt32) + ); + } else { + if (this.fmt.chunkSize > 18) { + extension = extension.concat( + pack(this.fmt.validBitsPerSample, this.uInt16) + ); + } + if (this.fmt.chunkSize > 20) { + extension = extension.concat(pack(this.fmt.dwChannelMask, this.uInt32)); + } + if (this.fmt.chunkSize > 24) { + extension = extension.concat( + pack(this.fmt.subformat[0], this.uInt32), + pack(this.fmt.subformat[1], this.uInt32), + pack(this.fmt.subformat[2], this.uInt32), + pack(this.fmt.subformat[3], this.uInt32) + ); + } } return extension; } @@ -356,15 +462,18 @@ export class WaveFileParser extends WaveFileReader { getLISTBytes_() { /** @type {!Array} */ let bytes = []; - for (let i=0; i} */ let subChunksBytes = this.getLISTSubChunksBytes_( - this.LIST[i].subChunks, this.LIST[i].format); + this.LIST[i].subChunks, + this.LIST[i].format + ); bytes = bytes.concat( packString(this.LIST[i].chunkId), pack(subChunksBytes.length + 4, this.uInt32), //chunkSize packString(this.LIST[i].format), - subChunksBytes); + subChunksBytes + ); } this.enforceByteLen_(bytes); return bytes; @@ -382,9 +491,9 @@ export class WaveFileParser extends WaveFileReader { /** @type {!Array} */ let bytes = []; for (let i = 0, len = subChunks.length; i < len; i++) { - if (format == 'INFO') { + if (format == "INFO") { bytes = bytes.concat(this.getLISTINFOSubChunksBytes_(subChunks[i])); - } else if (format == 'adtl') { + } else if (format == "adtl") { bytes = bytes.concat(this.getLISTadtlSubChunksBytes_(subChunks[i])); } this.enforceByteLen_(bytes); @@ -402,12 +511,12 @@ export class WaveFileParser extends WaveFileReader { /** @type {!Array} */ let bytes = []; /** @type {!Array} */ - let LISTsubChunkValue = writeString( - subChunk.value, subChunk.value.length); + let LISTsubChunkValue = writeString(subChunk.value, subChunk.value.length); bytes = bytes.concat( packString(subChunk.chunkId), pack(LISTsubChunkValue.length + 1, this.uInt32), //chunkSize - LISTsubChunkValue); + LISTsubChunkValue + ); bytes.push(0); return bytes; } @@ -421,20 +530,21 @@ export class WaveFileParser extends WaveFileReader { getLISTadtlSubChunksBytes_(subChunk) { /** @type {!Array} */ let bytes = []; - if (['labl', 'note'].indexOf(subChunk.chunkId) > -1) { + if (["labl", "note"].indexOf(subChunk.chunkId) > -1) { /** @type {!Array} */ let LISTsubChunkValue = writeString( - subChunk.value, - subChunk.value.length); + subChunk.value, + subChunk.value.length + ); bytes = bytes.concat( packString(subChunk.chunkId), pack(LISTsubChunkValue.length + 4 + 1, this.uInt32), //chunkSize pack(subChunk.dwName, this.uInt32), - LISTsubChunkValue); + LISTsubChunkValue + ); bytes.push(0); - } else if (subChunk.chunkId == 'ltxt') { - bytes = bytes.concat( - this.getLtxtChunkBytes_(subChunk)); + } else if (subChunk.chunkId == "ltxt") { + bytes = bytes.concat(this.getLtxtChunkBytes_(subChunk)); } return bytes; } @@ -456,9 +566,10 @@ export class WaveFileParser extends WaveFileReader { pack(ltxt.dwLanguage, this.uInt16), pack(ltxt.dwDialect, this.uInt16), pack(ltxt.dwCodePage, this.uInt16), - // should always be a empty string; - // kept for compatibility - writeString(ltxt.value, ltxt.value.length)); + // should always be a empty string; + // kept for compatibility + writeString(ltxt.value, ltxt.value.length) + ); } /** @@ -476,7 +587,8 @@ export class WaveFileParser extends WaveFileReader { bytes = bytes.concat( packString(this._PMX.chunkId), pack(this._PMX.chunkSize, this.uInt32), - _PMXPackedValue); + _PMXPackedValue + ); } this.enforceByteLen_(bytes); return bytes; @@ -493,7 +605,8 @@ export class WaveFileParser extends WaveFileReader { return bytes.concat( packString(this.junk.chunkId), pack(this.junk.chunkData.length, this.uInt32), //chunkSize - this.junk.chunkData); + this.junk.chunkData + ); } this.enforceByteLen_(bytes); return bytes; @@ -506,8 +619,10 @@ export class WaveFileParser extends WaveFileReader { * @private */ enforceByteLen_(bytes) { - if (bytes.length % 2) { - bytes.push(0); + if (this.padBytes) { + if (bytes.length % 2) { + bytes.push(0); + } } } } diff --git a/lib/wavefile-reader.js b/lib/wavefile-reader.js index 23afd03..355f0e4 100644 --- a/lib/wavefile-reader.js +++ b/lib/wavefile-reader.js @@ -27,26 +27,25 @@ * @see https://github.com/rochars/wavefile */ -import { RIFFFile } from './riff-file'; -import { unpackString, unpack } from './parsers/binary'; +import { RIFFFile } from "./riff-file"; +import { unpackString, unpack } from "./parsers/binary"; /** * A class to read wav files. * @extends RIFFFile */ export class WaveFileReader extends RIFFFile { - constructor() { super(); // Include 'RF64' as a supported container format - this.supported_containers.push('RF64'); + this.supported_containers.push("RF64"); /** * The data of the 'fmt' chunk. * @type {!Object} */ this.fmt = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -71,7 +70,28 @@ export class WaveFileReader extends RIFFFile { * 4 32-bit values representing a 128-bit ID * @type {!Array} */ - subformat: [] + subformat: [], + /** + * MPEG additional format information + * when audioFormat == 80 + * https://tech.ebu.ch/docs/tech/tech3285s1.pdf + */ + /** @type {number} */ + headLayer: 0, + /** @type {number} */ + headBitRate: 0, + /** @type {number} */ + headMode: 0, + /** @type {number} */ + headModeExt: 0, + /** @type {number} */ + headEmphasis: 0, + /** @type {number} */ + headFlags: 0, + /** @type {number} */ + ptsLow: 0, + /** @type {number} */ + ptsHigh: 0 }; /** * The data of the 'fact' chunk. @@ -79,7 +99,7 @@ export class WaveFileReader extends RIFFFile { */ this.fact = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -91,13 +111,13 @@ export class WaveFileReader extends RIFFFile { */ this.cue = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ dwCuePoints: 0, /** @type {!Array} */ - points: [], + points: [] }; /** * The data of the 'smpl' chunk. @@ -105,7 +125,7 @@ export class WaveFileReader extends RIFFFile { */ this.smpl = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -135,19 +155,19 @@ export class WaveFileReader extends RIFFFile { */ this.bext = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - description: '', //256 + description: "", //256 /** @type {string} */ - originator: '', //32 + originator: "", //32 /** @type {string} */ - originatorReference: '', //32 + originatorReference: "", //32 /** @type {string} */ - originationDate: '', //10 + originationDate: "", //10 /** @type {string} */ - originationTime: '', //8 + originationTime: "", //8 /** * 2 32-bit values, timeReference high and low * @type {!Array} @@ -156,7 +176,7 @@ export class WaveFileReader extends RIFFFile { /** @type {number} */ version: 0, //WORD /** @type {string} */ - UMID: '', // 64 chars + UMID: "", // 64 chars /** @type {number} */ loudnessValue: 0, //WORD /** @type {number} */ @@ -168,9 +188,79 @@ export class WaveFileReader extends RIFFFile { /** @type {number} */ maxShortTermLoudness: 0, //WORD /** @type {string} */ - reserved: '', //180 + reserved: "", //180 + /** @type {string} */ + codingHistory: "" // string, unlimited + }; + /** + * The data of the 'mext' chunk. + * @type {!Object} + */ + this.mext = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {number} */ + soundInformation: 0, + /** @type {number} */ + frameSize: 0, + /** @type {number} */ + ancillaryDataLength: 0, + /** @type {number} */ + ancillaryDataDef: 0, //4 /** @type {string} */ - codingHistory: '' // string, unlimited + reserved: "" + }; + /** + * The cart of the cart chunk. + * @type {!Object} + */ + this.cart = { + /** @type {string} */ + chunkId: "", + /** @type {number} */ + chunkSize: 0, + /** @type {string} */ + version: "", + /** @type {string} */ + title: "", + /** @type {string} */ + artist: "", + /** @type {string} */ + cutId: "", + /** @type {string} */ + clientId: "", + /** @type {string} */ + category: "", + /** @type {string} */ + classification: "", + /** @type {string} */ + outCue: "", + /** @type {string} */ + startDate: "", + /** @type {string} */ + startTime: "", + /** @type {string} */ + endDate: "", + /** @type {string} */ + endTime: "", + /** @type {string} */ + producerAppId: "", + /** @type {string} */ + producerAppVersion: "", + /** @type {string} */ + userDef: "", + /** @type {number} */ + levelReference: 0, + /** @type {!Array} */ + postTimer: [], + /** @type {string} */ + reserved: "", + /** @type {string} */ + url: "", + /** @type {string} */ + tagText: "" }; /** * The data of the 'iXML' chunk. @@ -178,11 +268,11 @@ export class WaveFileReader extends RIFFFile { */ this.iXML = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - value: '' + value: "" }; /** * The data of the 'ds64' chunk. @@ -191,7 +281,7 @@ export class WaveFileReader extends RIFFFile { */ this.ds64 = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {number} */ @@ -219,7 +309,7 @@ export class WaveFileReader extends RIFFFile { */ this.data = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {!Uint8Array} */ @@ -243,7 +333,7 @@ export class WaveFileReader extends RIFFFile { */ this.junk = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {!Array} */ @@ -255,21 +345,21 @@ export class WaveFileReader extends RIFFFile { */ this._PMX = { /** @type {string} */ - chunkId: '', + chunkId: "", /** @type {number} */ chunkSize: 0, /** @type {string} */ - value: '' + value: "" }; /** * @type {{be: boolean, bits: number, fp: boolean, signed: boolean}} * @protected */ - this.uInt16 = {bits: 16, be: false, signed: false, fp: false}; + this.uInt16 = { bits: 16, be: false, signed: false, fp: false }; } /** - * Set up the WaveFileReader object from a byte buffer. + * Set up the WaveFileReader object from a wav byte buffer. * @param {!Uint8Array} wavBuffer The buffer. * @param {boolean=} [samples=true] True if the samples should be loaded. * @throws {Error} If container is not RIFF, RIFX or RF64. @@ -277,18 +367,20 @@ export class WaveFileReader extends RIFFFile { * @throws {Error} If no 'fmt ' chunk is found. * @throws {Error} If no 'data' chunk is found. */ - fromBuffer(wavBuffer, samples=true) { + fromBuffer(wavBuffer, samples = true) { // Always should reset the chunks when reading from a buffer this.clearHeaders(); this.setSignature(wavBuffer); this.uInt16.be = this.uInt32.be; - if (this.format != 'WAVE') { + if (this.format != "WAVE") { throw Error('Could not find the "WAVE" format identifier'); } this.readDs64Chunk_(wavBuffer); this.readFmtChunk_(wavBuffer); this.readFactChunk_(wavBuffer); this.readBextChunk_(wavBuffer); + this.readMextChunk_(wavBuffer); + this.readCartChunk_(wavBuffer); this.readiXMLChunk_(wavBuffer); this.readCueChunk_(wavBuffer); this.readSmplChunk_(wavBuffer); @@ -311,6 +403,8 @@ export class WaveFileReader extends RIFFFile { Object.assign(this.cue, tmpWav.cue); Object.assign(this.smpl, tmpWav.smpl); Object.assign(this.bext, tmpWav.bext); + Object.assign(this.mext, tmpWav.mext); + Object.assign(this.cart, tmpWav.cart); Object.assign(this.iXML, tmpWav.iXML); Object.assign(this.ds64, tmpWav.ds64); Object.assign(this.data, tmpWav.data); @@ -318,7 +412,7 @@ export class WaveFileReader extends RIFFFile { Object.assign(this.junk, tmpWav.junk); Object.assign(this._PMX, tmpWav._PMX); } - + /** * Read the 'fmt ' chunk of a wave file. * @param {!Uint8Array} buffer The wav file buffer. @@ -327,7 +421,7 @@ export class WaveFileReader extends RIFFFile { */ readFmtChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('fmt '); + let chunk = this.findChunk("fmt "); if (chunk) { this.head = chunk.chunkData.start; this.fmt.chunkId = chunk.chunkId; @@ -352,7 +446,16 @@ export class WaveFileReader extends RIFFFile { readFmtExtension_(buffer) { if (this.fmt.chunkSize > 16) { this.fmt.cbSize = this.readUInt16_(buffer); - if (this.fmt.chunkSize > 18) { + if (this.fmt.audioFormat == 80 && this.fmt.chunkSize == 40) { + this.fmt.headLayer = this.readUInt16_(buffer); + this.fmt.headBitRate = this.readUInt32(buffer); + this.fmt.headMode = this.readUInt16_(buffer); + this.fmt.headModeExt = this.readUInt16_(buffer); + this.fmt.headEmphasis = this.readUInt16_(buffer); + this.fmt.headFlags = this.readUInt16_(buffer); + this.fmt.ptsLow = this.readUInt32(buffer); + this.fmt.ptsHigh = this.readUInt32(buffer); + } else if (this.fmt.chunkSize > 18) { this.fmt.validBitsPerSample = this.readUInt16_(buffer); if (this.fmt.chunkSize > 20) { this.fmt.dwChannelMask = this.readUInt32(buffer); @@ -360,7 +463,8 @@ export class WaveFileReader extends RIFFFile { this.readUInt32(buffer), this.readUInt32(buffer), this.readUInt32(buffer), - this.readUInt32(buffer)]; + this.readUInt32(buffer) + ]; } } } @@ -373,7 +477,7 @@ export class WaveFileReader extends RIFFFile { */ readFactChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('fact'); + let chunk = this.findChunk("fact"); if (chunk) { this.head = chunk.chunkData.start; this.fact.chunkId = chunk.chunkId; @@ -389,7 +493,7 @@ export class WaveFileReader extends RIFFFile { */ readCueChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('cue '); + let chunk = this.findChunk("cue "); if (chunk) { this.head = chunk.chunkData.start; this.cue.chunkId = chunk.chunkId; @@ -402,7 +506,7 @@ export class WaveFileReader extends RIFFFile { fccChunk: this.readString(buffer, 4), dwChunkStart: this.readUInt32(buffer), dwBlockStart: this.readUInt32(buffer), - dwSampleOffset: this.readUInt32(buffer), + dwSampleOffset: this.readUInt32(buffer) }); } } @@ -415,7 +519,7 @@ export class WaveFileReader extends RIFFFile { */ readSmplChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('smpl'); + let chunk = this.findChunk("smpl"); if (chunk) { this.head = chunk.chunkData.start; this.smpl.chunkId = chunk.chunkId; @@ -436,7 +540,7 @@ export class WaveFileReader extends RIFFFile { dwStart: this.readUInt32(buffer), dwEnd: this.readUInt32(buffer), dwFraction: this.readUInt32(buffer), - dwPlayCount: this.readUInt32(buffer), + dwPlayCount: this.readUInt32(buffer) }); } } @@ -451,14 +555,15 @@ export class WaveFileReader extends RIFFFile { */ readDataChunk_(buffer, samples) { /** @type {?Object} */ - let chunk = this.findChunk('data'); + let chunk = this.findChunk("data"); if (chunk) { - this.data.chunkId = 'data'; + this.data.chunkId = "data"; this.data.chunkSize = chunk.chunkSize; if (samples) { this.data.samples = buffer.slice( chunk.chunkData.start, - chunk.chunkData.end); + chunk.chunkData.end + ); } } else { throw Error('Could not find the "data" chunk'); @@ -472,7 +577,7 @@ export class WaveFileReader extends RIFFFile { */ readBextChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('bext'); + let chunk = this.findChunk("bext"); if (chunk) { this.head = chunk.chunkData.start; this.bext.chunkId = chunk.chunkId; @@ -484,7 +589,8 @@ export class WaveFileReader extends RIFFFile { this.bext.originationTime = this.readString(buffer, 8); this.bext.timeReference = [ this.readUInt32(buffer), - this.readUInt32(buffer)]; + this.readUInt32(buffer) + ]; this.bext.version = this.readUInt16_(buffer); this.bext.UMID = this.readString(buffer, 64); this.bext.loudnessValue = this.readUInt16_(buffer); @@ -494,7 +600,70 @@ export class WaveFileReader extends RIFFFile { this.bext.maxShortTermLoudness = this.readUInt16_(buffer); this.bext.reserved = this.readString(buffer, 180); this.bext.codingHistory = this.readString( - buffer, this.bext.chunkSize - 602); + buffer, + this.bext.chunkSize - 602 + ); + } + } + + /** + * Read the 'mext' chunk of a wav file. + * @param {!Uint8Array} buffer The wav file buffer. + * @private + */ + readMextChunk_(buffer) { + /** @type {?Object} */ + let chunk = this.findChunk("mext"); + if (chunk) { + this.head = chunk.chunkData.start; + this.mext.chunkId = chunk.chunkId; + this.mext.chunkSize = chunk.chunkSize; + this.mext.soundInformation = this.readUInt16_(buffer); + this.mext.frameSize = this.readUInt16_(buffer); + this.mext.ancillaryDataLength = this.readUInt16_(buffer); + this.mext.ancillaryDataDef = this.readUInt16_(buffer); + this.mext.reserved = this.readString(buffer, 4); + } + } + + /** + * Read the 'cart' chunk of a wav file. + * @param {!Uint8Array} buffer The wav file buffer. + * @private + */ + readCartChunk_(buffer) { + /** @type {?Object} */ + let chunk = this.findChunk("cart"); + if (chunk) { + this.head = chunk.chunkData.start; + this.cart.chunkId = chunk.chunkId; + this.cart.chunkSize = chunk.chunkSize; + this.cart.version = this.readString(buffer, 4); + this.cart.title = this.readString(buffer, 64); + this.cart.artist = this.readString(buffer, 64); + this.cart.cutId = this.readString(buffer, 64); + this.cart.clientId = this.readString(buffer, 64); + this.cart.category = this.readString(buffer, 64); + this.cart.classification = this.readString(buffer, 64); + this.cart.outCue = this.readString(buffer, 64); + this.cart.startDate = this.readString(buffer, 10); + this.cart.startTime = this.readString(buffer, 8); + this.cart.endDate = this.readString(buffer, 10); + this.cart.endTime = this.readString(buffer, 8); + this.cart.producerAppId = this.readString(buffer, 64); + this.cart.producerAppVersion = this.readString(buffer, 64); + this.cart.userDef = this.readString(buffer, 64); + this.cart.levelReference = this.readUInt32(buffer); + this.cart.postTimer = []; + for (let i = 0; i < 8; i++) { + this.cart.postTimer.push({ + usage: this.readString(buffer, 4), + value: this.readUInt32(buffer) + }); + } + this.cart.reserved = this.readString(buffer, 276); + this.cart.url = this.readString(buffer, 1024); + this.cart.tagText = this.readString(buffer, chunk.chunkSize - 2048); } } @@ -505,13 +674,16 @@ export class WaveFileReader extends RIFFFile { */ readiXMLChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('iXML'); + let chunk = this.findChunk("iXML"); if (chunk) { this.head = chunk.chunkData.start; this.iXML.chunkId = chunk.chunkId; this.iXML.chunkSize = chunk.chunkSize; this.iXML.value = unpackString( - buffer, this.head, this.head + this.iXML.chunkSize); + buffer, + this.head, + this.head + this.iXML.chunkSize + ); } } @@ -523,7 +695,7 @@ export class WaveFileReader extends RIFFFile { */ readDs64Chunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('ds64'); + let chunk = this.findChunk("ds64"); if (chunk) { this.head = chunk.chunkData.start; this.ds64.chunkId = chunk.chunkId; @@ -542,7 +714,7 @@ export class WaveFileReader extends RIFFFile { // 32, 32 + wav.ds64.tableLength); //} } else { - if (this.container == 'RF64') { + if (this.container == "RF64") { throw Error('Could not find the "ds64" chunk'); } } @@ -555,19 +727,23 @@ export class WaveFileReader extends RIFFFile { */ readLISTChunk_(buffer) { /** @type {?Object} */ - let listChunks = this.findChunk('LIST', true); + let listChunks = this.findChunk("LIST", true); if (listChunks !== null) { - for (let j=0; j < listChunks.length; j++) { + for (let j = 0; j < listChunks.length; j++) { /** @type {!Object} */ let subChunk = listChunks[j]; this.LIST.push({ chunkId: subChunk.chunkId, chunkSize: subChunk.chunkSize, format: subChunk.format, - subChunks: []}); - for (let x=0; x -1) { + if (format == "adtl") { + if (["labl", "note", "ltxt"].indexOf(subChunk.chunkId) > -1) { this.readLISTadtlSubChunks_(buffer, subChunk); } - // RIFF INFO tags like ICRD, ISFT, ICMT - } else if(format == 'INFO') { + // RIFF INFO tags like ICRD, ISFT, ICMT + } else if (format == "INFO") { this.readLISTINFOSubChunks_(buffer, subChunk); } } @@ -605,14 +781,14 @@ export class WaveFileReader extends RIFFFile { chunkSize: subChunk.chunkSize, dwName: this.readUInt32(buffer) }; - if (subChunk.chunkId == 'ltxt') { + if (subChunk.chunkId == "ltxt") { item.dwSampleLength = this.readUInt32(buffer); item.dwPurposeID = this.readUInt32(buffer); item.dwCountry = this.readUInt16_(buffer); item.dwLanguage = this.readUInt16_(buffer); item.dwDialect = this.readUInt16_(buffer); item.dwCodePage = this.readUInt16_(buffer); - item.value = ''; // kept for compatibility + item.value = ""; // kept for compatibility } else { item.value = this.readZSTR_(buffer, this.head); } @@ -641,14 +817,14 @@ export class WaveFileReader extends RIFFFile { */ readJunkChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('junk'); + let chunk = this.findChunk("junk"); if (chunk) { this.junk = { chunkId: chunk.chunkId, chunkSize: chunk.chunkSize, - chunkData: [].slice.call(buffer.slice( - chunk.chunkData.start, - chunk.chunkData.end)) + chunkData: [].slice.call( + buffer.slice(chunk.chunkData.start, chunk.chunkData.end) + ) }; } } @@ -660,13 +836,16 @@ export class WaveFileReader extends RIFFFile { */ read_PMXChunk_(buffer) { /** @type {?Object} */ - let chunk = this.findChunk('_PMX'); + let chunk = this.findChunk("_PMX"); if (chunk) { this.head = chunk.chunkData.start; this._PMX.chunkId = chunk.chunkId; this._PMX.chunkSize = chunk.chunkSize; this._PMX.value = unpackString( - buffer, this.head, this.head + this._PMX.chunkSize); + buffer, + this.head, + this.head + this._PMX.chunkSize + ); } } @@ -677,7 +856,7 @@ export class WaveFileReader extends RIFFFile { * @return {string} The string. * @private */ - readZSTR_(bytes, index=0) { + readZSTR_(bytes, index = 0) { for (let i = index; i < bytes.length; i++) { this.head++; if (bytes[i] === 0) { diff --git a/package-lock.json b/package-lock.json index 1389490..5c619d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,69 @@ { - "name": "wavefile", - "version": "11.0.0", + "name": "prx-wavefile", + "version": "11.0.0-prx.1", "lockfileVersion": 1, "requires": true, "dependencies": { - "@ampproject/rollup-plugin-closure-compiler": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@ampproject/rollup-plugin-closure-compiler/-/rollup-plugin-closure-compiler-0.13.0.tgz", - "integrity": "sha512-uvbNj/ngTZxHZM7y1RLC+hQ1qUZ9JH0BVdVmcAWRH6U/25ek18V/r+ks9VKmkzRfn8Oe9nY4XmXHJozfQGVOIQ==", + "@ampproject/remapping": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-0.2.0.tgz", + "integrity": "sha512-a4EztS9/GOVQjX5Ol+Iz33TFhaXvYBF7aB6D8+Qz0/SCIxOm3UNRhGZiwcCuJ8/Ifc6NCogp3S48kc5hFxRpUw==", "dev": true, "requires": { - "acorn": "7.1.0", - "acorn-dynamic-import": "4.0.0", - "acorn-walk": "7.0.0", - "google-closure-compiler": "20191111.0.0", - "magic-string": "0.25.4", - "temp-write": "4.0.0" + "@jridgewell/resolve-uri": "1.0.0", + "sourcemap-codec": "1.4.8" + }, + "dependencies": { + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + } + } + }, + "@ampproject/rollup-plugin-closure-compiler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ampproject/rollup-plugin-closure-compiler/-/rollup-plugin-closure-compiler-0.27.0.tgz", + "integrity": "sha512-stpAOn2ZZEJuAV39HFw9cnKJYNhEeHtcsoa83orpLDhSxsxSbVEKwHaWlFBaQYpQRSOdapC4eJhJnCzocZxnqg==", + "dev": true, + "requires": { + "@ampproject/remapping": "0.2.0", + "acorn": "7.3.1", + "acorn-walk": "7.1.1", + "estree-walker": "2.0.1", + "google-closure-compiler": "20210808.0.0", + "magic-string": "0.25.7", + "uuid": "8.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "estree-walker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz", + "integrity": "sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==", + "dev": true + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "uuid": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", + "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==", + "dev": true + } } }, "@babel/code-frame": { @@ -24,7 +72,7 @@ "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { - "@babel/highlight": "7.8.3" + "@babel/highlight": "^7.8.3" } }, "@babel/generator": { @@ -33,10 +81,10 @@ "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", "dev": true, "requires": { - "@babel/types": "7.8.3", - "jsesc": "2.5.2", - "lodash": "4.17.15", - "source-map": "0.5.7" + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" } }, "@babel/helper-function-name": { @@ -45,9 +93,9 @@ "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.8.3", - "@babel/template": "7.8.3", - "@babel/types": "7.8.3" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { @@ -56,7 +104,7 @@ "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "7.8.3" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { @@ -65,7 +113,7 @@ "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "7.8.3" + "@babel/types": "^7.8.3" } }, "@babel/highlight": { @@ -74,9 +122,9 @@ "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { - "chalk": "2.4.2", - "esutils": "2.0.3", - "js-tokens": "4.0.0" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { @@ -91,9 +139,9 @@ "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "@babel/parser": "7.8.3", - "@babel/types": "7.8.3" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { @@ -102,15 +150,15 @@ "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "@babel/generator": "7.8.3", - "@babel/helper-function-name": "7.8.3", - "@babel/helper-split-export-declaration": "7.8.3", - "@babel/parser": "7.8.3", - "@babel/types": "7.8.3", - "debug": "4.1.1", - "globals": "11.12.0", - "lodash": "4.17.15" + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.3", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" }, "dependencies": { "debug": { @@ -119,7 +167,7 @@ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.1" } } } @@ -130,22 +178,28 @@ "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { - "esutils": "2.0.3", - "lodash": "4.17.15", - "to-fast-properties": "2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" } }, + "@jridgewell/resolve-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz", + "integrity": "sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==", + "dev": true + }, "@rollup/plugin-commonjs": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.1.tgz", "integrity": "sha512-SaVUoaLDg3KnIXC5IBNIspr1APTYDzk05VaYcI6qz+0XX3ZlSCwAkfAhNSOxfd5GAdcm/63Noi4TowOY9MpcDg==", "dev": true, "requires": { - "@rollup/pluginutils": "3.0.4", - "estree-walker": "0.6.1", - "is-reference": "1.1.4", - "magic-string": "0.25.4", - "resolve": "1.14.2" + "@rollup/pluginutils": "^3.0.0", + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0" } }, "@rollup/plugin-node-resolve": { @@ -154,11 +208,11 @@ "integrity": "sha512-Cv7PDIvxdE40SWilY5WgZpqfIUEaDxFxs89zCAHjqyRwlTSuql4M5hjIuc5QYJkOH0/vyiyNXKD72O+LhRipGA==", "dev": true, "requires": { - "@rollup/pluginutils": "3.0.4", + "@rollup/pluginutils": "^3.0.0", "@types/resolve": "0.0.8", - "builtin-modules": "3.1.0", - "is-module": "1.0.0", - "resolve": "1.14.2" + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.11.1" } }, "@rollup/pluginutils": { @@ -167,7 +221,7 @@ "integrity": "sha512-buc0oeq2zqQu2mpMyvZgAaQvitikYjT/4JYhA4EXwxX8/g0ZGHoGiX+0AwmfhrNqH4oJv67gn80sTZFQ/jL1bw==", "dev": true, "requires": { - "estree-walker": "0.6.1" + "estree-walker": "^0.6.1" } }, "@types/estree": { @@ -188,7 +242,7 @@ "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", "dev": true, "requires": { - "@types/node": "13.1.7" + "@types/node": "*" } }, "acorn": { @@ -197,16 +251,10 @@ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", - "dev": true - }, "acorn-walk": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.0.0.tgz", - "integrity": "sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", "dev": true }, "agent-base": { @@ -215,7 +263,7 @@ "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "requires": { - "es6-promisify": "5.0.0" + "es6-promisify": "^5.0.0" } }, "ansi-colors": { @@ -236,7 +284,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.3" + "color-convert": "^1.9.0" } }, "append-transform": { @@ -245,7 +293,7 @@ "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "default-require-extensions": "2.0.0" + "default-require-extensions": "^2.0.0" } }, "archy": { @@ -260,7 +308,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "argv": { @@ -287,7 +335,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -315,10 +363,10 @@ "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", "dev": true, "requires": { - "hasha": "3.0.0", - "make-dir": "2.1.0", - "package-hash": "3.0.0", - "write-file-atomic": "2.4.3" + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" }, "dependencies": { "make-dir": { @@ -327,8 +375,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "semver": { @@ -351,7 +399,7 @@ "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", "dev": true, "requires": { - "lodash": "4.17.15" + "lodash": "^4.17.14" } }, "chalk": { @@ -360,9 +408,9 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "cli": { @@ -372,7 +420,7 @@ "dev": true, "requires": { "exit": "0.1.2", - "glob": "7.1.6" + "glob": "^7.1.1" } }, "cliui": { @@ -381,9 +429,9 @@ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "3.1.0", - "strip-ansi": "5.2.0", - "wrap-ansi": "5.1.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { @@ -398,9 +446,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "7.0.3", - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "5.2.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { @@ -409,7 +457,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -438,9 +486,9 @@ "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, "requires": { - "inherits": "2.0.4", - "process-nextick-args": "2.0.1", - "readable-stream": "2.3.7" + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" } }, "codecov": { @@ -449,11 +497,11 @@ "integrity": "sha512-IUJB6WG47nWK7o50etF8jBadxdMw7DmoQg05yIljstXFBGB6clOZsIj6iD4P82T2YaIU3qq+FFu8K9pxgkCJDQ==", "dev": true, "requires": { - "argv": "0.0.2", - "ignore-walk": "3.0.3", - "js-yaml": "3.13.1", - "teeny-request": "3.11.3", - "urlgrey": "0.4.4" + "argv": "^0.0.2", + "ignore-walk": "^3.0.1", + "js-yaml": "^3.13.1", + "teeny-request": "^3.11.3", + "urlgrey": "^0.4.4" } }, "color-convert": { @@ -489,7 +537,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "0.1.4" + "date-now": "^0.1.4" } }, "convert-source-map": { @@ -498,7 +546,7 @@ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.1" } }, "core-util-is": { @@ -513,11 +561,11 @@ "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "make-dir": "2.1.0", - "nested-error-stacks": "2.1.0", - "pify": "4.0.1", - "safe-buffer": "5.1.2" + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" }, "dependencies": { "make-dir": { @@ -526,8 +574,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "semver": { @@ -544,8 +592,8 @@ "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { - "lru-cache": "4.1.5", - "which": "1.3.1" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } }, "date-now": { @@ -560,7 +608,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.1" } }, "decamelize": { @@ -575,7 +623,7 @@ "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "strip-bom": "3.0.0" + "strip-bom": "^3.0.0" } }, "define-properties": { @@ -584,7 +632,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "1.1.1" + "object-keys": "^1.0.12" } }, "diff": { @@ -605,8 +653,8 @@ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "dev": true, "requires": { - "domelementtype": "2.0.1", - "entities": "2.0.0" + "domelementtype": "^2.0.1", + "entities": "^2.0.0" }, "dependencies": { "domelementtype": { @@ -635,7 +683,7 @@ "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", "dev": true, "requires": { - "domelementtype": "1.3.1" + "domelementtype": "1" } }, "domutils": { @@ -644,8 +692,8 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0.2.2", - "domelementtype": "1.3.1" + "dom-serializer": "0", + "domelementtype": "1" } }, "emoji-regex": { @@ -666,7 +714,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { @@ -675,17 +723,17 @@ "integrity": "sha512-YoKuru3Lyoy7yVTBSH2j7UxTqe/je3dWAruC0sHvZX1GNd5zX8SSLvQqEgO9b3Ex8IW+goFI9arEEsFIbulhOw==", "dev": true, "requires": { - "es-to-primitive": "1.2.1", - "function-bind": "1.1.1", - "has": "1.0.3", - "has-symbols": "1.0.1", - "is-callable": "1.1.5", - "is-regex": "1.0.5", - "object-inspect": "1.7.0", - "object-keys": "1.1.1", - "object.assign": "4.1.0", - "string.prototype.trimleft": "2.1.1", - "string.prototype.trimright": "2.1.1" + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" } }, "es-to-primitive": { @@ -694,9 +742,9 @@ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "is-callable": "1.1.5", - "is-date-object": "1.0.2", - "is-symbol": "1.0.3" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "es6-error": { @@ -717,7 +765,7 @@ "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { - "es6-promise": "4.2.8" + "es6-promise": "^4.0.3" } }, "escape-string-regexp": { @@ -762,9 +810,9 @@ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "2.1.0", - "pkg-dir": "3.0.0" + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" }, "dependencies": { "make-dir": { @@ -773,8 +821,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "semver": { @@ -791,7 +839,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "flat": { @@ -800,7 +848,7 @@ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "is-buffer": "2.0.4" + "is-buffer": "~2.0.3" } }, "foreground-child": { @@ -809,8 +857,8 @@ "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { - "cross-spawn": "4.0.2", - "signal-exit": "3.0.2" + "cross-spawn": "^4", + "signal-exit": "^3.0.0" } }, "fs.realpath": { @@ -837,12 +885,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "globals": { @@ -852,52 +900,45 @@ "dev": true }, "google-closure-compiler": { - "version": "20191111.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20191111.0.0.tgz", - "integrity": "sha512-Ji+FaqYKXNbJ78N5Do6hu61mPrB9D+8xSnmRO59u2CrIbmNCtoFnqkCAAL4Ye8LR8foRqtkDdiKJSblpe+5ttg==", + "version": "20210808.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20210808.0.0.tgz", + "integrity": "sha512-+R2+P1tT1lEnDDGk8b+WXfyVZgWjcCK9n1mmZe8pMEzPaPWxqK7GMetLVWnqfTDJ5Q+LRspOiFBv3Is+0yuhCA==", "dev": true, "requires": { - "chalk": "2.4.2", - "google-closure-compiler-java": "20191111.0.0", - "google-closure-compiler-js": "20191111.0.0", - "google-closure-compiler-linux": "20191111.0.0", - "google-closure-compiler-osx": "20191111.0.0", - "google-closure-compiler-windows": "20191111.0.0", - "minimist": "1.2.0", - "vinyl": "2.2.0", - "vinyl-sourcemaps-apply": "0.2.1" + "chalk": "2.x", + "google-closure-compiler-java": "^20210808.0.0", + "google-closure-compiler-linux": "^20210808.0.0", + "google-closure-compiler-osx": "^20210808.0.0", + "google-closure-compiler-windows": "^20210808.0.0", + "minimist": "1.x", + "vinyl": "2.x", + "vinyl-sourcemaps-apply": "^0.2.0" } }, "google-closure-compiler-java": { - "version": "20191111.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20191111.0.0.tgz", - "integrity": "sha512-JgsQtJVVgDuj50VPQV1OgHxMy78daTL3d607V6zF/qETl3rEEiazEGfXDGUX/1V1SUOW+Y03Ct9bcO3LMxNlxg==", - "dev": true - }, - "google-closure-compiler-js": { - "version": "20191111.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20191111.0.0.tgz", - "integrity": "sha512-W3Oy5NF5CMgUv31CMbgLN7zhdCTVMTnNI//iEPYMotzXfX8viyOnTMipDDwJNeMnY7lfXbQrYOo+QkWP6WzZtg==", + "version": "20210808.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20210808.0.0.tgz", + "integrity": "sha512-7dEQfBzOdwdjwa/Pq8VAypNBKyWRrOcKjnNYOO9gEg2hjh8XVMeQzTqw4uANfVvvANGdE/JjD+HF6zHVgLRwjg==", "dev": true }, "google-closure-compiler-linux": { - "version": "20191111.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20191111.0.0.tgz", - "integrity": "sha512-pxxB83Ae7G9OHFSOEzOYlp844YyqQgU1RXBpCGBKeDAQrs3dykHqRhNGZdI7N1tsby/pT+hKL1US2l/CslPqag==", + "version": "20210808.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20210808.0.0.tgz", + "integrity": "sha512-byKi5ITUiWRvEIcQo76i1siVnOwrTmG+GNcBG4cJ7x8IE6+4ki9rG5pUe4+DOYHkfk52XU6XHt9aAAgCcFDKpg==", "dev": true, "optional": true }, "google-closure-compiler-osx": { - "version": "20191111.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20191111.0.0.tgz", - "integrity": "sha512-sviL1DLN+dDfYBLzmU6+2Yk19P1uzUHQuKaXrHxEYZkU4jTJNq/TC4xi6t0ZgvLQEm2Vf7xhOrt80TYKnoQaVg==", + "version": "20210808.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20210808.0.0.tgz", + "integrity": "sha512-iwyAY6dGj1FrrBdmfwKXkjtTGJnqe8F+9WZbfXxiBjkWLtIsJt2dD1+q7g/sw3w8mdHrGQAdxtDZP/usMwj/Rg==", "dev": true, "optional": true }, "google-closure-compiler-windows": { - "version": "20191111.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20191111.0.0.tgz", - "integrity": "sha512-iKdz2bWrrM4zLv3USCRtWX4kLWzZhbj/afh/W6giLxg5XQzbNg+UpVF+2G6f/LEYK9UNBgS2TdyNTuw8mod+Mg==", + "version": "20210808.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20210808.0.0.tgz", + "integrity": "sha512-VI+UUYwtGWDYwpiixrWRD8EklHgl6PMbiEaHxQSrQbH8PDXytwaOKqmsaH2lWYd5Y/BOZie2MzjY7F5JI69q1w==", "dev": true, "optional": true }, @@ -919,7 +960,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-flag": { @@ -940,7 +981,7 @@ "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", "dev": true, "requires": { - "is-stream": "1.1.0" + "is-stream": "^1.0.1" }, "dependencies": { "is-stream": { @@ -975,11 +1016,11 @@ "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { - "domelementtype": "1.3.1", - "domhandler": "2.3.0", - "domutils": "1.5.1", - "entities": "1.0.0", - "readable-stream": "1.1.14" + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" }, "dependencies": { "entities": { @@ -1000,10 +1041,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -1020,8 +1061,8 @@ "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { - "agent-base": "4.3.0", - "debug": "3.2.6" + "agent-base": "^4.3.0", + "debug": "^3.1.0" } }, "ignore-walk": { @@ -1030,7 +1071,7 @@ "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "imurmurhash": { @@ -1045,8 +1086,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -1106,22 +1147,16 @@ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has": "1.0.3" + "has": "^1.0.3" } }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "1.0.1" + "has-symbols": "^1.0.1" } }, "isarray": { @@ -1148,7 +1183,7 @@ "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", "dev": true, "requires": { - "append-transform": "1.0.0" + "append-transform": "^1.0.0" } }, "istanbul-lib-instrument": { @@ -1157,13 +1192,13 @@ "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "requires": { - "@babel/generator": "7.8.3", - "@babel/parser": "7.8.3", - "@babel/template": "7.8.3", - "@babel/traverse": "7.8.3", - "@babel/types": "7.8.3", - "istanbul-lib-coverage": "2.0.5", - "semver": "6.3.0" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" } }, "istanbul-lib-report": { @@ -1172,9 +1207,9 @@ "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "requires": { - "istanbul-lib-coverage": "2.0.5", - "make-dir": "2.1.0", - "supports-color": "6.1.0" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "dependencies": { "make-dir": { @@ -1183,8 +1218,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "semver": { @@ -1199,7 +1234,7 @@ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -1210,11 +1245,11 @@ "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { - "debug": "4.1.1", - "istanbul-lib-coverage": "2.0.5", - "make-dir": "2.1.0", - "rimraf": "2.7.1", - "source-map": "0.6.1" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" }, "dependencies": { "debug": { @@ -1223,7 +1258,7 @@ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.1" } }, "make-dir": { @@ -1232,8 +1267,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "semver": { @@ -1256,7 +1291,7 @@ "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", "dev": true, "requires": { - "html-escaper": "2.0.0" + "html-escaper": "^2.0.0" } }, "js-tokens": { @@ -1271,8 +1306,8 @@ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "js2xmlparser": { @@ -1281,7 +1316,7 @@ "integrity": "sha512-WuNgdZOXVmBk5kUPMcTcVUpbGRzLfNkv7+7APq7WiDihpXVKrgxo6wwRpRl9OQeEBgKCVk9mR7RbzrnNWC8oBw==", "dev": true, "requires": { - "xmlcreate": "2.0.1" + "xmlcreate": "^2.0.0" } }, "jsdoc": { @@ -1290,20 +1325,20 @@ "integrity": "sha512-Yf1ZKA3r9nvtMWHO1kEuMZTlHOF8uoQ0vyo5eH7SQy5YeIiHM+B0DgKnn+X6y6KDYZcF7G2SPkKF+JORCXWE/A==", "dev": true, "requires": { - "@babel/parser": "7.8.3", - "bluebird": "3.7.2", - "catharsis": "0.8.11", - "escape-string-regexp": "2.0.0", - "js2xmlparser": "4.0.0", - "klaw": "3.0.0", - "markdown-it": "8.4.2", - "markdown-it-anchor": "5.2.5", - "marked": "0.7.0", - "mkdirp": "0.5.1", - "requizzle": "0.2.3", - "strip-json-comments": "3.0.1", + "@babel/parser": "^7.4.4", + "bluebird": "^3.5.4", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.0", + "klaw": "^3.0.0", + "markdown-it": "^8.4.2", + "markdown-it-anchor": "^5.0.2", + "marked": "^0.7.0", + "mkdirp": "^0.5.1", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.0.1", "taffydb": "2.6.2", - "underscore": "1.9.2" + "underscore": "~1.9.1" }, "dependencies": { "escape-string-regexp": { @@ -1326,14 +1361,14 @@ "integrity": "sha512-ooaD/hrBPhu35xXW4gn+o3SOuzht73gdBuffgJzrZBJZPGgGiiTvJEgTyxFvBO2nz0+X1G6etF8SzUODTlLY6Q==", "dev": true, "requires": { - "cli": "1.0.1", - "console-browserify": "1.1.0", - "exit": "0.1.2", - "htmlparser2": "3.8.3", - "lodash": "4.17.15", - "minimatch": "3.0.4", - "shelljs": "0.3.0", - "strip-json-comments": "1.0.4" + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.11", + "minimatch": "~3.0.2", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x" }, "dependencies": { "strip-json-comments": { @@ -1356,7 +1391,7 @@ "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.9" } }, "linkify-it": { @@ -1365,7 +1400,7 @@ "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", "dev": true, "requires": { - "uc.micro": "1.0.6" + "uc.micro": "^1.0.1" } }, "load-json-file": { @@ -1374,10 +1409,10 @@ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" }, "dependencies": { "pify": { @@ -1394,8 +1429,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "lodash": { @@ -1416,7 +1451,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.4.2" + "chalk": "^2.0.1" } }, "lru-cache": { @@ -1425,8 +1460,8 @@ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "magic-string": { @@ -1435,16 +1470,7 @@ "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", "dev": true, "requires": { - "sourcemap-codec": "1.4.7" - } - }, - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "6.3.0" + "sourcemap-codec": "^1.4.4" } }, "markdown-it": { @@ -1453,11 +1479,11 @@ "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", "dev": true, "requires": { - "argparse": "1.0.10", - "entities": "1.1.2", - "linkify-it": "2.2.0", - "mdurl": "1.0.1", - "uc.micro": "1.0.6" + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" } }, "markdown-it-anchor": { @@ -1484,7 +1510,7 @@ "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "source-map": "0.6.1" + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -1501,13 +1527,13 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { @@ -1564,12 +1590,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "ms": { @@ -1590,7 +1616,7 @@ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -1619,8 +1645,8 @@ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", "dev": true, "requires": { - "object.getownpropertydescriptors": "2.1.0", - "semver": "5.7.1" + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" }, "dependencies": { "semver": { @@ -1643,10 +1669,10 @@ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "hosted-git-info": "2.8.5", - "resolve": "1.14.2", - "semver": "5.7.1", - "validate-npm-package-license": "3.0.4" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" }, "dependencies": { "semver": { @@ -1663,31 +1689,31 @@ "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", "dev": true, "requires": { - "archy": "1.0.0", - "caching-transform": "3.0.2", - "convert-source-map": "1.7.0", - "cp-file": "6.2.0", - "find-cache-dir": "2.1.0", - "find-up": "3.0.0", - "foreground-child": "1.5.6", - "glob": "7.1.6", - "istanbul-lib-coverage": "2.0.5", - "istanbul-lib-hook": "2.0.7", - "istanbul-lib-instrument": "3.3.0", - "istanbul-lib-report": "2.0.8", - "istanbul-lib-source-maps": "3.0.6", - "istanbul-reports": "2.2.7", - "js-yaml": "3.13.1", - "make-dir": "2.1.0", - "merge-source-map": "1.1.0", - "resolve-from": "4.0.0", - "rimraf": "2.7.1", - "signal-exit": "3.0.2", - "spawn-wrap": "1.4.3", - "test-exclude": "5.2.3", - "uuid": "3.3.3", - "yargs": "13.3.0", - "yargs-parser": "13.1.1" + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" }, "dependencies": { "make-dir": { @@ -1696,8 +1722,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "semver": { @@ -1726,10 +1752,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1", - "has-symbols": "1.0.1", - "object-keys": "1.1.1" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, "object.getownpropertydescriptors": { @@ -1738,8 +1764,8 @@ "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.17.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "once": { @@ -1748,7 +1774,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -1763,7 +1789,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -1772,7 +1798,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.0.0" } }, "p-try": { @@ -1787,10 +1813,10 @@ "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "hasha": "3.0.0", - "lodash.flattendeep": "4.4.0", - "release-zalgo": "1.0.0" + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" } }, "parse-json": { @@ -1799,8 +1825,8 @@ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "path-exists": { @@ -1827,7 +1853,7 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { @@ -1850,7 +1876,7 @@ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "3.0.0" + "find-up": "^3.0.0" } }, "process-nextick-args": { @@ -1871,9 +1897,9 @@ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.5.0", - "path-type": "3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, "read-pkg-up": { @@ -1882,8 +1908,8 @@ "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { - "find-up": "3.0.0", - "read-pkg": "3.0.0" + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } }, "readable-stream": { @@ -1892,13 +1918,13 @@ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "isarray": "1.0.0", - "process-nextick-args": "2.0.1", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "release-zalgo": { @@ -1907,7 +1933,7 @@ "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { - "es6-error": "4.1.1" + "es6-error": "^4.0.1" } }, "remove-trailing-separator": { @@ -1917,9 +1943,9 @@ "dev": true }, "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", "dev": true }, "require-directory": { @@ -1940,7 +1966,7 @@ "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", "dev": true, "requires": { - "lodash": "4.17.15" + "lodash": "^4.17.14" } }, "resolve": { @@ -1949,7 +1975,7 @@ "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", "dev": true, "requires": { - "path-parse": "1.0.6" + "path-parse": "^1.0.6" } }, "resolve-from": { @@ -1964,18 +1990,18 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } }, "rollup": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.29.0.tgz", - "integrity": "sha512-V63Iz0dSdI5qPPN5HmCN6OBRzBFhMqNWcvwgq863JtSCTU6Vdvqq6S2fYle/dSCyoPrBkIP3EIr1RVs3HTRqqg==", + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", "dev": true, "requires": { - "@types/estree": "0.0.39", - "@types/node": "13.1.7", - "acorn": "7.1.0" + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" } }, "safe-buffer": { @@ -2026,12 +2052,12 @@ "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", "dev": true, "requires": { - "foreground-child": "1.5.6", - "mkdirp": "0.5.1", - "os-homedir": "1.0.2", - "rimraf": "2.7.1", - "signal-exit": "3.0.2", - "which": "1.3.1" + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" } }, "spdx-correct": { @@ -2040,8 +2066,8 @@ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.5" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -2056,8 +2082,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.5" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -2078,8 +2104,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "string.prototype.trimleft": { @@ -2088,8 +2114,8 @@ "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", "dev": true, "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1" + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" } }, "string.prototype.trimright": { @@ -2098,8 +2124,8 @@ "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", "dev": true, "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1" + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" } }, "string_decoder": { @@ -2108,7 +2134,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -2117,7 +2143,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { @@ -2138,7 +2164,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "taffydb": { @@ -2153,28 +2179,9 @@ "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", "dev": true, "requires": { - "https-proxy-agent": "2.2.4", - "node-fetch": "2.6.0", - "uuid": "3.3.3" - } - }, - "temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true - }, - "temp-write": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz", - "integrity": "sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw==", - "dev": true, - "requires": { - "graceful-fs": "4.2.3", - "is-stream": "2.0.0", - "make-dir": "3.0.0", - "temp-dir": "1.0.0", - "uuid": "3.3.3" + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" } }, "test-exclude": { @@ -2183,10 +2190,10 @@ "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", "dev": true, "requires": { - "glob": "7.1.6", - "minimatch": "3.0.4", - "read-pkg-up": "4.0.0", - "require-main-filename": "2.0.0" + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" } }, "to-fast-properties": { @@ -2237,22 +2244,22 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "3.1.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, "requires": { - "clone": "2.1.2", - "clone-buffer": "1.0.0", - "clone-stats": "1.0.0", - "cloneable-readable": "1.1.3", - "remove-trailing-separator": "1.1.0", - "replace-ext": "1.0.0" + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" } }, "vinyl-sourcemaps-apply": { @@ -2261,7 +2268,7 @@ "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "^0.5.1" } }, "which": { @@ -2270,7 +2277,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -2285,7 +2292,7 @@ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "string-width": "2.1.1" + "string-width": "^1.0.2 || 2" } }, "wrap-ansi": { @@ -2294,9 +2301,9 @@ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "string-width": "3.1.0", - "strip-ansi": "5.2.0" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { "ansi-regex": { @@ -2311,9 +2318,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "7.0.3", - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "5.2.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { @@ -2322,7 +2329,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -2339,9 +2346,9 @@ "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" } }, "xmlcreate": { @@ -2368,16 +2375,16 @@ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "dev": true, "requires": { - "cliui": "5.0.0", - "find-up": "3.0.0", - "get-caller-file": "2.0.5", - "require-directory": "2.1.1", - "require-main-filename": "2.0.0", - "set-blocking": "2.0.0", - "string-width": "3.1.0", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "13.1.1" + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" }, "dependencies": { "ansi-regex": { @@ -2392,9 +2399,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "7.0.3", - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "5.2.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { @@ -2403,7 +2410,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -2414,8 +2421,8 @@ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } }, "yargs-unparser": { @@ -2424,9 +2431,9 @@ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { - "flat": "4.1.0", - "lodash": "4.17.15", - "yargs": "13.3.0" + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" } } } diff --git a/package.json b/package.json index 409e3f5..0237931 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,16 @@ { - "name": "wavefile", - "version": "11.0.0", + "name": "prx-wavefile", + "version": "11.0.0-prx.4", "description": "Create, read and write wav files according to the specs.", - "homepage": "https://github.com/rochars/wavefile", + "homepage": "https://github.com/PRX/prx-wavefile", "author": "Rafael da Silva Rocha ", "license": "MIT", "main": "./dist/wavefile.js", "module": "./index.js", + "exports": { + "import": "./dist/wrapper.mjs", + "require": "./dist/wavefile.js" + }, "types": "./index.d.ts", "bin": "./bin/wavefile.js", "engines": { @@ -41,10 +45,10 @@ ], "repository": { "type": "git", - "url": "git://github.com/rochars/wavefile.git" + "url": "git://github.com/PRX/prx-wavefile.git" }, "bugs": { - "url": "https://github.com/rochars/wavefile/issues" + "url": "https://github.com/PRX/prx-wavefile/issues" }, "directories": { "bin": "bin", @@ -76,7 +80,7 @@ "test-sr-sinc": "node ./node_modules/mocha/bin/_mocha test/resampler-full/sinc.js test/resampler-full/sinc-IIR.js test/resampler-full/sinc-no-lpf.js -R dot --timeout=1600000", "test-umd": "node ./node_modules/mocha/bin/_mocha test/resampler test/dist test/src --umd --recursive -R dot --timeout=240000", "test-tsc": "tsc ./test/TypeScript/index.ts && node -r esm ./test/TypeScript/index.js", - "test-cli": "wavefile ./test/files/M1F1-int12WE-AFsp.wav --tag=ICMT && wavefile ./test/files/M1F1-int12WE-AFsp.wav --resample=16000 --method=point ./test/files/out/to-sample-rate/M1F1-int12WE-AFsp-CLI.wav", + "test-cli": "prx-wavefile ./test/files/M1F1-int12WE-AFsp.wav --tag=ICMT && prx-wavefile ./test/files/M1F1-int12WE-AFsp.wav --resample=16000 --method=point ./test/files/out/to-sample-rate/M1F1-int12WE-AFsp-CLI.wav", "test-dist": "npm run test-umd && npm run test-tsc && npm run test-cli", "rollup-bundle": "rollup -c && npm run test-dist", "doc": "./node_modules/.bin/jsdoc -c .jsdocrc -d docs -r README.md -t node_modules/docdash", @@ -84,7 +88,7 @@ "coverage": "nyc report --reporter=lcov > coverage.lcov && codecov" }, "devDependencies": { - "@ampproject/rollup-plugin-closure-compiler": "^0.13.0", + "@ampproject/rollup-plugin-closure-compiler": "^0.27.0", "@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-node-resolve": "^6.0.0", "byte-data": "^19.0.1", diff --git a/test/dist/cart/cart-rw.js b/test/dist/cart/cart-rw.js new file mode 100644 index 0000000..d37fc13 --- /dev/null +++ b/test/dist/cart/cart-rw.js @@ -0,0 +1,205 @@ +/** + * WaveFile: https://github.com/rochars/wavefile + * Copyright (c) 2020 Andrew Kuklewicz. MIT License. + * + * Test reading and writing the "cart" chunk. + */ + +const assert = require("assert"); +const fs = require("fs"); +const WaveFile = require("../../../test/loader.js"); +const path = "./test/files/"; + +function cl(str) { + return str.replace(/\0+$/, ""); +} + +describe("Write file without pad bytes", function() { + let wav = new WaveFile(fs.readFileSync(path + "cc_0101.wav")); + wav.cart.tagText = "1"; + wav.padBytes = false; + fs.writeFileSync(path + "out/cc_0101_nopad.wav", wav.toBuffer()); + fs.stat(path + "out/cc_0101_nopad.wav", (error, stats) => { + if (error) { + throw error; + } + else { + assert.equal(stats.size, 806911); + } + }); + assert.equal(wav.cart.chunkSize, 2049); + + wav = new WaveFile(fs.readFileSync(path + "cc_0101.wav")); + wav.cart.tagText = "1"; + wav.padBytes = true; + fs.writeFileSync(path + "out/cc_0101_yespad.wav", wav.toBuffer()); + fs.stat(path + "out/cc_0101_yespad.wav", (error, stats) => { + if (error) { + throw error; + } + else { + assert.equal(stats.size, 806912); + } + }); + assert.equal(wav.cart.chunkSize, 2049); +}); + +describe("Read and write cart chunk PCM wav file", function() { + let wav = new WaveFile(fs.readFileSync(path + "cc_0101.wav")); + fs.writeFileSync(path + "out/cc_0101.wav", wav.toBuffer()); + wav = new WaveFile(fs.readFileSync(path + "out/cc_0101.wav")); + + assert.equal(wav.fmt.audioFormat, 1); + assert.equal(wav.fmt.numChannels, 2); + assert.equal(wav.fmt.sampleRate, 32000); + assert.equal(wav.fmt.byteRate, 128000); + + assert.equal(wav.cart.chunkId, "cart"); + assert.equal(wav.cart.chunkSize, 2568); + assert.equal(wav.cart.version, "0101"); + assert.equal( + cl(wav.cart.title), + "Cart Chunk: the traffic data file format for the Radio Industry" + ); + assert.equal(cl(wav.cart.artist), "Jay Rose, dplay.com"); + assert.equal(cl(wav.cart.cutId), "DEMO-0101"); + assert.equal(cl(wav.cart.clientId), "CartChunk.org"); + assert.equal(cl(wav.cart.category), "DEMO"); + assert.equal(cl(wav.cart.classification), "Demo and sample files"); + assert.equal(cl(wav.cart.outCue), "the Radio Industry"); + assert.equal(cl(wav.cart.startDate), "1900/01/01"); + assert.equal(cl(wav.cart.startTime), "00:00:00"); + assert.equal(cl(wav.cart.endDate), "2099/12/31"); + assert.equal(cl(wav.cart.endTime), "23:59:59"); + assert.equal(cl(wav.cart.producerAppId), "AUDICY"); + assert.equal(cl(wav.cart.producerAppVersion), "3.10/623"); + assert.equal( + cl(wav.cart.userDef), + "Demo ID showing basic 'cart' chunk attributes" + ); + assert.equal(wav.cart.levelReference, 32768); + assert.equal(wav.cart.postTimer[0].usage, "MRK "); + assert.equal(wav.cart.postTimer[0].value, 112000); + assert.equal(wav.cart.postTimer[6].usage, "\u0000\u0000\u0000\u0000"); + assert.equal(wav.cart.postTimer[6].value, 4294967295); + assert.equal(wav.cart.postTimer[7].usage, "EOD "); + assert.equal(wav.cart.postTimer[7].value, 201024); + assert.equal(cl(wav.cart.reserved), ""); + assert.equal(cl(wav.cart.url), "http://www.cartchunk.org"); + + assert.equal( + wav.cart.tagText, + "The radio traffic data, or 'cart' format utilizes a widely\r\nused standard audio file format (wave and broadcast wave file).\r\nIt incorporates the common broadcast-specific cart labeling\r\ninformation into a specialized chunk within the file itself.\r\nAs a result, the burden of linking multiple systems is reduced\r\nto producer applications writing a single file, and the consumer\r\napplications reading it. The destination application can extract\r\ninformation and insert it into the native database application\r\nas needed.\r\n" + ); +}); + +describe("Read and write a cart bwf mpeg file", function() { + let wav = new WaveFile(fs.readFileSync(path + "60315.wav")); + fs.writeFileSync(path + "out/60315.wav", wav.toBuffer()); + wav = new WaveFile(fs.readFileSync(path + "out/60315.wav")); + + it("fmt should have extended mpeg info", function() { + assert.equal(wav.fmt.audioFormat, 80); + assert.equal(wav.fmt.byteRate, 32000); + assert.equal(wav.fmt.headLayer, 2); + assert.equal(wav.fmt.headBitRate, 256000); + assert.equal(wav.fmt.headMode, 1); + assert.equal(wav.fmt.headModeExt, 1); + assert.equal(wav.fmt.headEmphasis, 0); + assert.equal(wav.fmt.headFlags, 28); + assert.equal(wav.fmt.ptsLow, 0); + assert.equal(wav.fmt.ptsHigh, 0); + }); + + it("cart chunkId should be 'cart' when read or written", function() { + assert.equal(wav.cart.chunkId, "cart"); + assert.equal(wav.cart.version, "0101"); + assert.equal(cl(wav.cart.cutId), "60315"); + }); + + it("mext chunkId should be 'mext' when read or written", function() { + assert.equal(wav.mext.chunkId, "mext"); + assert.equal(wav.mext.frameSize, 835); + assert.equal(wav.mext.soundInformation, 7); + }); + + it("should have MPEG audio format and bit depth", function() { + assert.equal(wav.bitDepth, 65535); + assert.equal(wav.fmt.audioFormat, 80); + }); +}); + +describe("Create an bwf mpeg cart file from info and an mp2 file", function() { + let info = { + version: 1, + layer: 2, + sampleRate: 44100, + bitRate: 128, + channelMode: "stereo", + padding: 1, + modeExtension: 0, + emphasis: 0, + privateBit: 1, + copyright: true, + original: true, + errorProtection: true, + numChannels: 2, + frameSize: 768, + sampleLength: 269568, + freeForm: true + }; + + let wav = new WaveFile(); + wav.fromMpeg(fs.readFileSync(path + "test.mp2"), info); + wav.cart.chunkId = "cart"; + wav.cart.cutId = "30000"; + wav.cart.title = "Title"; + wav.cart.artist = "Artist"; + + fs.writeFileSync(path + "out/test-mp2-i.wav", wav.toBuffer()); + wav = new WaveFile(fs.readFileSync(path + "out/test-mp2-i.wav")); + + assert.equal(wav.fmt.sampleRate, 44100); + assert.equal(wav.fmt.byteRate, 16000); + assert.equal(wav.fmt.numChannels, 2); + assert.equal(wav.fmt.blockAlign, 768); + assert.equal(wav.fmt.numChannels, 2); + assert.equal(wav.fmt.headBitRate, 128000); + assert.equal(wav.fmt.headLayer, 2); + + assert.equal(wav.fact.dwSampleLength, 269568); + + assert.equal( + wav.bext.codingHistory, + "A=MPEG1L2,F=44100,B=128,M=stereo,T=wavefile\r\n\u0000\u0000" + ); + + assert.equal(wav.mext.frameSize, 768); + assert.equal(wav.mext.soundInformation, 8); +}); + +describe("Create a bwf mpeg cart file from an mp2 file", function() { + let wav = new WaveFile(); + + wav.fromMpeg(fs.readFileSync(path + "44100_test.mp2")); + fs.writeFileSync(path + "out/test-mp2.wav", wav.toBuffer()); + wav = new WaveFile(fs.readFileSync(path + "out/test-mp2.wav")); + + assert.equal(wav.fmt.sampleRate, 44100); + assert.equal(wav.fmt.byteRate, 32000); + assert.equal(wav.fmt.numChannels, 2); + assert.equal(wav.fmt.blockAlign, 835); + assert.equal(wav.fmt.numChannels, 2); + assert.equal(wav.fmt.headBitRate, 256000); + assert.equal(wav.fmt.headLayer, 2); + + assert.equal(wav.fact.dwSampleLength, 1323648); + + assert.equal( + wav.bext.codingHistory, + "A=MPEG1L2,F=44100,B=256,M=stereo,T=wavefile\r\n\u0000\u0000" + ); + + assert.equal(wav.mext.frameSize, 835); + assert.equal(wav.mext.soundInformation, 7); +}); diff --git a/test/files/44100_test.mp2 b/test/files/44100_test.mp2 new file mode 100644 index 0000000..52692d1 Binary files /dev/null and b/test/files/44100_test.mp2 differ diff --git a/test/files/60315.wav b/test/files/60315.wav new file mode 100644 index 0000000..c0f3fa1 Binary files /dev/null and b/test/files/60315.wav differ diff --git a/test/files/ORIGINS.md b/test/files/ORIGINS.md index 5e255f5..c97cc8a 100644 --- a/test/files/ORIGINS.md +++ b/test/files/ORIGINS.md @@ -1,10 +1,21 @@ # Origin of publicly available test files ## http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html + - M1F1-int12WE-AFsp.wav ## http://mauvecloud.net/sounds/index.html + - ima22.wav ## https://github.com/robsaunders/zodak/tree/master/resources -- smpl_cue.wav \ No newline at end of file + +- smpl_cue.wav + +## http://www.cartchunk.org/samples.htm + +- cc_0101.wav + +## https://www.prss.org/contentdepot-stations-sas.html + +- 60315.wav diff --git a/test/files/cc_0101.wav b/test/files/cc_0101.wav new file mode 100644 index 0000000..04cb881 Binary files /dev/null and b/test/files/cc_0101.wav differ diff --git a/test/files/out/60315.wav b/test/files/out/60315.wav new file mode 100644 index 0000000..aa1eb22 Binary files /dev/null and b/test/files/out/60315.wav differ diff --git a/test/files/out/cc_0101.wav b/test/files/out/cc_0101.wav new file mode 100644 index 0000000..06cbe52 Binary files /dev/null and b/test/files/out/cc_0101.wav differ diff --git a/test/files/out/cc_0101_nopad.wav b/test/files/out/cc_0101_nopad.wav new file mode 100644 index 0000000..3131f5b Binary files /dev/null and b/test/files/out/cc_0101_nopad.wav differ diff --git a/test/files/out/cc_0101_yespad.wav b/test/files/out/cc_0101_yespad.wav new file mode 100644 index 0000000..8f2855a Binary files /dev/null and b/test/files/out/cc_0101_yespad.wav differ diff --git a/test/files/out/test-mp2-i.wav b/test/files/out/test-mp2-i.wav new file mode 100644 index 0000000..09c375e Binary files /dev/null and b/test/files/out/test-mp2-i.wav differ diff --git a/test/files/out/test-mp2.wav b/test/files/out/test-mp2.wav new file mode 100644 index 0000000..8aa58eb Binary files /dev/null and b/test/files/out/test-mp2.wav differ diff --git a/test/files/test-bad.mp2 b/test/files/test-bad.mp2 new file mode 100644 index 0000000..e2e4c6b Binary files /dev/null and b/test/files/test-bad.mp2 differ diff --git a/test/files/test-id3.mp2 b/test/files/test-id3.mp2 new file mode 100644 index 0000000..4e2195e Binary files /dev/null and b/test/files/test-id3.mp2 differ diff --git a/test/files/test.mp2 b/test/files/test.mp2 new file mode 100644 index 0000000..db9daba Binary files /dev/null and b/test/files/test.mp2 differ diff --git a/test/lib/mpeg-reader.js b/test/lib/mpeg-reader.js new file mode 100644 index 0000000..f6d9a92 --- /dev/null +++ b/test/lib/mpeg-reader.js @@ -0,0 +1,48 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = "./test/files/"; +const MpegReader = require("../../lib/mpeg-reader.js").MpegReader; + +describe("Test the mpeg file class", function() { + let mpeg = new MpegReader(fs.readFileSync(path + "test.mp2")); + let mpegId3 = new MpegReader(fs.readFileSync(path + "test-id3.mp2")); + + it("should parse an mp2 file", function() { + assert.equal(mpeg.head, 4); + assert.equal(mpeg.version, 1); + assert.equal(mpeg.layer, 2); + assert.equal(mpeg.errorProtection, false); + assert.equal(mpeg.bitRate, 256); + assert.equal(mpeg.sampleRate, 48000); + assert.equal(mpeg.padding, false); + assert.equal(mpeg.privateBit, false); + assert.equal(mpeg.channelMode, "stereo"); + assert.equal(mpeg.modeExtension, 0); + assert.equal(mpeg.copyright, false); + assert.equal(mpeg.original, false); + assert.equal(mpeg.emphasis, 0); + assert.equal(mpeg.numChannels, 2); + assert.equal(mpeg.id3v2Offset, 0); + assert.equal(mpeg.samplesPerFrame, 1152); + assert.equal(mpeg.frameSize, 768); + assert.equal(mpeg.sampleLength, 269568); + assert.equal(mpeg.durationEstimate | 0, 5); + assert.equal(mpeg.homogeneous, true); + assert.equal(mpeg.freeForm, false); + }); + + it("should parse the same mp3 with id3 tags", function() { + assert.equal(mpegId3.id3v2Offset, 315); + assert.equal(mpeg.version, mpegId3.version); + assert.equal(mpeg.layer, mpegId3.layer); + assert.equal(mpeg.bitRate, mpegId3.bitRate); + assert.equal(mpeg.sampleRate, mpegId3.sampleRate); + assert.equal(mpeg.channelMode, mpegId3.channelMode); + }); + + it("should fail to parse a bad mp2", function() { + assert.throws(function() { + let mpegBad = new MpegReader(fs.readFileSync(path + "test-bad.mp2")); + }, /Invalid frame/); + }); +}); diff --git a/test/src/interface.js b/test/src/interface.js index 4ea8fbc..4d8b210 100644 --- a/test/src/interface.js +++ b/test/src/interface.js @@ -3,62 +3,63 @@ * Copyright (c) 2017-2018 Rafael da Silva Rocha. MIT License. * * Tests for the WaveFile API. - * + * */ -var assert = assert || require('assert'); -var WaveFile = WaveFile || require('../loader.js'); +var assert = assert || require("assert"); +var WaveFile = WaveFile || require("../loader.js"); -describe('API properties', function() { +describe("API properties", function() { + wav = new WaveFile(); - wav = new WaveFile(); - - // WaveFile - it('should create a WaveFile object', function() { - assert.ok(wav); - }); + // WaveFile + it("should create a WaveFile object", function() { + assert.ok(wav); + }); - // properties - it('should have a fmt chunk property', function() { - assert.ok(wav.fmt); - }); - it('should have a fact chunk property', function() { - assert.ok(wav.fact); - }); - it('should have a cue chunk property', function() { - assert.ok(wav.cue); - }); - it('should have a smpl chunk property', function() { - assert.ok(wav.smpl); - }); - it('should have a bext chunk property', function() { - assert.ok(wav.bext); - }); - it('should have a ds64 chunk property', function() { - assert.ok(wav.ds64); - }); - it('should have a data chunk property', function() { - assert.ok(wav.data); - }); - it('should have a LIST chunk property', function() { - assert.ok(wav.LIST); - }); - it('should have a junk chunk property', function() { - assert.ok(wav.junk); - }); + // properties + it("should have a fmt chunk property", function() { + assert.ok(wav.fmt); + }); + it("should have a fact chunk property", function() { + assert.ok(wav.fact); + }); + it("should have a cue chunk property", function() { + assert.ok(wav.cue); + }); + it("should have a smpl chunk property", function() { + assert.ok(wav.smpl); + }); + it("should have a bext chunk property", function() { + assert.ok(wav.bext); + }); + it("should have a mext chunk property", function() { + assert.ok(wav.mext); + }); + it("should have a cart chunk property", function() { + assert.ok(wav.cart); + }); + it("should have a ds64 chunk property", function() { + assert.ok(wav.ds64); + }); + it("should have a data chunk property", function() { + assert.ok(wav.data); + }); + it("should have a LIST chunk property", function() { + assert.ok(wav.LIST); + }); + it("should have a junk chunk property", function() { + assert.ok(wav.junk); + }); }); +describe("API Methods", function() { + wav = new WaveFile(); -describe('API Methods', function() { - - wav = new WaveFile(); - - // methods - it('should return a Uint8Array', function() { - wav.fromScratch(1, 8000, '8', [0,0], {container: 'RIFX'}); - var buffer = wav.toBuffer(); - assert.equal( - buffer.constructor, - new Uint8Array().constructor); - }); -}); \ No newline at end of file + // methods + it("should return a Uint8Array", function() { + wav.fromScratch(1, 8000, "8", [0, 0], { container: "RIFX" }); + var buffer = wav.toBuffer(); + assert.equal(buffer.constructor, new Uint8Array().constructor); + }); +});

    NameTypeDescription
    _PMXValue + + +string + + + + The value for the _PMX chunk.
    A object with the data of the cue point. # Only required attribute to create a cue point: pointData.position: The position of the point in milliseconds # Optional attribute for cue points: pointData.label: A string label for the cue point # Extra data used for regions pointData.end: A number representing the end of the region, in milliseconds, counting from the start of the file. If no end attr is specified then no region is created. # You may also specify the following attrs for regions, all optional: pointData.dwPurposeID pointData.dwCountry pointData.dwLanguage pointData.dwDialect pointData.dwCodePageA object with the data of the cue point. + +# Only required attribute to create a cue point: +pointData.position: The position of the point in milliseconds + +# Optional attribute for cue points: +pointData.label: A string label for the cue point + +# Extra data used for regions +pointData.end: A number representing the end of the region, + in milliseconds, counting from the start of the file. If + no end attr is specified then no region is created. + +# You may also specify the following attrs for regions, all optional: +pointData.dwPurposeID +pointData.dwCountry +pointData.dwLanguage +pointData.dwDialect +pointData.dwCodePage
    The new bit depth of the samples. One of '8' ... '32' (integers), '32f' or '64' (floats)The new bit depth of the samples. + One of '8' ... '32' (integers), '32f' or '64' (floats)
    A boolean indicating if the resolution of samples should be actually changed or not.A boolean indicating if the + resolution of samples should be actually changed or not.