wavefile
Copyright (c) 2017-2019 Rafael da Silva Rocha.
https://github.com/rochars/wavefile
Notice
My country, Brazil, is under a fascist government that is hunting and killing its opponents. I've been threatened too.
Create, read and write wav files according to the specs.
- MIT licensed
- Use it in the browser (IE10+)
- Use it in Node.js
- Use it as a command line tool
- Handle files up to 2GB
With wavefile you can:
And more.
Install
npm install wavefile
To use it from the command line, install it globally:
npm install wavefile -g
Use
Node
const wavefile = require('wavefile');
let wav = new wavefile.WaveFile();
or
const WaveFile = require('wavefile').WaveFile;
let wav = new WaveFile();
or
import { WaveFile } from 'wavefile';
let wav = new WaveFile();
Browser
Use the wavefile.js file in the dist folder:
<script src="wavefile.js"></script>
<script>
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 unpkg:
<script src="https://unpkg.com/wavefile"></script>
Browser compatibility
IE10+. Should work in all modern browsers.
Cross-browser tests powered by
Command line
To see the available options:
wavefile --help
The available options:
--bitdepth Ex: wavefile input.wav --bitdepth=32f output.wav
Change the bit depth.
The input file is not affected.
Possible values: 8, 16, 24, 32, 32f, 64
--compress Ex: wavefile input.wav --compress=adpcm output.wav
Apply compression to the file.
The input file is not affected.
Possible values: adpcm, alaw, mulaw
--tag Ex: wavefile input.wav --tag=ICRD
Print the value of tag if the tag exists.
--list-tags Ex: wavefile input.wav --list-tags
Print all tags of the file.
--list-cue Ex: wavefile input.wav --list-cue
Print all the cue points of the file.
--bits Ex: wavefile input.wav --bits
Print the bit depth of the file.
--rate Ex: wavefile input.wav --rate
Print the sample rate of the file.
--help Ex: --help
Show this help page.
Node.js Example
const WaveFile = require('wavefile').WaveFile;
let wav = new WaveFile(buffer);
console.log(wav.container);
console.log(wav.chunkSize);
console.log(wav.fmt.chunkId);
let wavBuffer = wav.toBuffer();
let wavDataURI = wav.toDataURI();
Table of Contents
Operation Manual
Create wave files from scratch
Use the fromScratch(numChannels, sampleRate, bitDepth, samples)
method.
Mono:
let wav = new WaveFile();
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.
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
"8a" - 8-bit A-Law
"8m" - 8-bit mu-Law
"16" - 16-bit
"24" - 24-bit
"32" - 32-bit
"32f" - 32-bit floating point
"64" - 64-bit floating point
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
const WaveFile = require('wavefile').WaveFile;
wav = new WaveFile();
wav.fromBuffer(buffer);
wav.fromBase64(base64);
wav.fromDataURI(dataURI);
Add RIFF tags to files
You can create (or overwrite) tags on files with the WaveFile.setTag() method.
wav.setTag("ICMT", "some comments");
To get the value of a tag (if it exists), use WaveFile.getTag():
console.log(wav.getTag("ICMT"));
You can delete a tag with WaveFile.deleteTag():
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:
wav.setCuePoint({position: 1500});
You can also create cue points with labels by defining a label attribute:
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);
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:
let cuePoints = wav.listCuePoints();
This method will return a list with cue points ordered as they appear in the file.
[
{
position: 500,
label: 'cue marker 1',
end: 1500,
dwName: 1,
dwPosition: 0,
fccChunk: 'data',
dwChunkStart: 0,
dwBlockStart: 0,
dwSampleOffset: 22050,
dwSampleLength: 3646827,
dwPurposeID: 544106354,
dwCountry: 0,
dwLanguage: 0,
dwDialect: 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:
wav.setCuePoint({position: 1500, end: 2500, label: 'some label'});
You can also define the following optional properties when creating a region:
- dwPurposeID
- dwCountry
- dwLanguage
- dwDialect
- 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"});
RIFX to RIFF and RIFF to RIFX:
wav.toRIFX();
wav.toRIFF();
IMA-ADPCM
16-bit 8000 Hz mono wave files can be compressed as IMA-ADPCM:
wav.toIMAADPCM();
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:
wav.fromIMAADPCM();
Decoding always result in 16-bit audio. To decode to another bit depth:
wav.fromIMAADPCM("24");
A-Law
16-bit wave files (mono or stereo) can be encoded as 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:
wav.fromALaw();
Decoding always result in 16-bit audio. To decode to another bit depth:
wav.fromALaw("24");
mu-Law
16-bit wave files (mono or stereo) can be encoded as 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:
wav.fromMuLaw();
Decoding always result in 16-bit audio. To decode to another bit depth:
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.
let wav = new WaveFile(fs.readFileSync("32bit-file.wav"));
wav.toBitDepth("24");
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.
let wav = new WaveFile(fs.readFileSync("16kHz-file.wav"));
wav.toSampleRate(44100);
fs.writeFileSync("44100Hz-file.wav", wav.toBuffer());
To use another method:
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:
wav.toSampleRate(44100, {method: "sinc", LPF: false});
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:
wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'});
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:
wav.fromMuLaw();
wav.toSampleRate(44100, {method: "sinc"});
wav.toMuLaw();
Add BWF metadata
To add BWF data to a file you can use the bext property:
let wav = new WaveFile(fs.readFileSync("32bit-file.wav"));
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.
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.
XML Chunks
wavefile support reading and writing iXML and _PMX chunks.
To get the value of iXML or _PMX chunks:
let iXMLValue = wav.getiXML();
let _PMXValue = wav.get_PMX();
To set the value of iXML or _PMX chunks:
wav.setiXML(iXMLValue);
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 samples
Samples are stored in data.samples as a Uint8Array.
To get the samples as a Float64Array you should use the getSamples() method:
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.
samples = wav.getSamples();
samples = wav.getSamples(false);
samples = wav.getSamples(true);
You can use any typed array as the output of getSamples():
samples = wav.getSamples(false, Int32Array);
let samples = getSamples(false, Int16Array);
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:
wav = new WaveFile();
let samples = [561, 1200, 423];
wav.fromScratch(1, 8000, "16", samples);
wav.getSample(1);
wav.setSample(1, 10);
wav.getSample(1);
Range:
- 0 to 255 for 8-bit
- -32768 to 32767 for 16-bit
- -8388608 to 8388607 for 24-bit
- -2147483648 to 2147483647 for 32-bit
- -1.0 to 1.0 for 32-bit (float)
- -1.0 to 1.0 for 64-bit (float)
API
To create a WaveFile object:
WaveFile(bytes=null);
The WaveFile methods
WaveFile.fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {}
WaveFile.fromBuffer(bytes) {}
WaveFile.toBuffer() {}
WaveFile.fromBase64(base64String) {}
WaveFile.toBase64() {}
WaveFile.toDataURI() {}
WaveFile.fromDataURI(dataURI) {}
WaveFile.toRIFF() {}
WaveFile.toRIFX() {}
WaveFile.toBitDepth(bitDepth, changeResolution=true) {}
WaveFile.toSampleRate(sampleRate, details={}) {};
WaveFile.toIMAADPCM() {}
WaveFile.fromIMAADPCM(bitDepth='16') {}
WaveFile.toALaw() {}
WaveFile.fromALaw(bitDepth='16') {}
WaveFile.toMuLaw() {}
WaveFile.fromMuLaw(bitDepth='16') {}
WaveFile.setTag(tag, value) {}
WaveFile.getTag(tag) {}
WaveFile.deleteTag(tag) {}
WaveFile.listTags() {}
WaveFile.setCuePoint(pointData) {}
WaveFile.deleteCuePoint(index) {}
WaveFile.listCuePoints() {}
WaveFile.updateLabel(pointIndex, label) {}
WaveFile.getSamples(interleaved=false, OutputObject=Float64Array) {};
WaveFile.getSample(index) {};
WaveFile.setSample(index, sample) {};
WaveFile.getiXML() {};
WaveFile.setiXML(iXMLValue) {};
WaveFile.get_PMX() {};
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:
[
{
position: 500,
label: 'cue marker 1',
end: 1500,
dwName: 1,
dwPosition: 0,
fccChunk: 'data',
dwChunkStart: 0,
dwBlockStart: 0,
dwSampleOffset: 22050,
dwSampleLength: 3646827,
dwPurposeID: 544106354,
dwCountry: 0,
dwLanguage: 0,
dwDialect: 0,
dwCodePage: 0
},
]
The list order reflects the order of the points in the file.
The WaveFile properties
WaveFile.container = '';
WaveFile.chunkSize = 0;
WaveFile.format = '';
WaveFile.fmt = {
chunkId: '',
chunkSize: 0,
audioFormat: 0,
numChannels: 0,
sampleRate: 0,
byteRate: 0,
blockAlign: 0,
bitsPerSample: 0,
cbSize: 0,
validBitsPerSample: 0,
dwChannelMask: 0,
subformat: []
};
WaveFile.fact = {
chunkId: '',
chunkSize: 0,
dwSampleLength: 0
};
WaveFile.cue = {
chunkId: '',
chunkSize: 0,
dwCuePoints: 0,
points: [],
};
WaveFile.smpl = {
chunkId: '',
chunkSize: 0,
dwManufacturer: 0,
dwProduct: 0,
dwSamplePeriod: 0,
dwMIDIUnityNote: 0,
dwMIDIPitchFraction: 0,
dwSMPTEFormat: 0,
dwSMPTEOffset: 0,
dwNumSampleLoops: 0,
dwSamplerData: 0,
loops: [],
};
WaveFile.bext = {
chunkId: '',
chunkSize: 0,
description: '',
originator: '',
originatorReference: '',
originationDate: '',
originationTime: '',
timeReference: [0, 0],
version: 0,
UMID: '',
loudnessValue: 0,
loudnessRange: 0,
maxTruePeakLevel: 0,
maxMomentaryLoudness: 0,
maxShortTermLoudness: 0,
reserved: '',
codingHistory: ''
};
WaveFile.iXML = {
chunkId: '',
chunkSize: 0,
value: ''
};
WaveFile.ds64 = {
chunkId: '',
chunkSize: 0,
riffSizeHigh: 0,
riffSizeLow: 0,
dataSizeHigh: 0,
dataSizeLow: 0,
originationTime: 0,
sampleCountHigh: 0,
sampleCountLow: 0,
};
WaveFile.data = {
chunkId: '',
chunkSize: 0,
samples: new Uint8Array(0)
};
WaveFile.LIST = [];
WaveFile.junk = {
chunkId: '',
chunkSize: 0,
chunkData: []
};
WaveFile._PMX = {
chunkId: '',
chunkSize: 0,
value: ''
};
WaveFile.bitDepth = '';
Cue points
Items in cue.points are objects like this:
{
dwName: 0,
dwPosition: 0,
fccChunk: 0,
dwChunkStart: 0,
dwBlockStart: 0,
dwSampleOffset: 0
}
Sample loops
Items in smpl.loops are objects like this:
{
dwName: '',
dwType: 0,
dwStart: 0,
dwEnd: 0,
dwFraction: 0,
dwPlayCount: 0
}
LIST chunk
"LIST" chunk data is stored as follows:
WaveFile.LIST = [];
Items in WaveFile.LIST are objects like this:
{
chunkId: '',
chunkSize: 0,
format: '',
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:
{
chunkId: '',
chunkSize 0,
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 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, also available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
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
Software
https://github.com/erikd/libsndfile
https://gist.github.com/hackNightly/3776503
https://github.com/chirlu/sox/blob/master/src/wav.c
Other
https://developercertificate.org/
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
https://google.github.io/styleguide/jsguide.html
Legal
LICENSE
Copyright (c) 2017-2019 Rafael da Silva Rocha.
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.