Giter Club home page Giter Club logo

wavefile's Introduction

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

wavefile

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
  • Zero dependencies

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 use

To see the available options:

wavefile --help

Node.js Example

const WaveFile = require('wavefile').WaveFile;

// Load a wav file buffer as a WaveFile object
let wav = new WaveFile(buffer);

// Check some of the file properties
console.log(wav.container);
console.log(wav.chunkSize);
console.log(wav.fmt.chunkId);

// Call toBuffer() to get the bytes of the file.
// You can write the output straight to disk:
let wavBuffer = wav.toBuffer();

// Call toDataURI() to get the file as a DataURI:
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();

// Create a mono wave file, 44.1 kHz, 32-bit and 4 samples
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]
]);
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();
// Read a wav file from a buffer
wav.fromBuffer(buffer);
// Read a wav file from a base64 string
wav.fromBase64(base64);
// Read a wav file from a data URI
wav.fromDataURI(dataURI);

Add RIFF tags to files

You can create (or overwrite) tags on files with the WaveFile.setTag() method.

// 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():

console.log(wav.getTag("ICMT"));
// some comments

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:

// to create a cue point
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'});

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, // 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, // the region length as a sample count
    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:

// to create a region with a 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
  • 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:

// Turn a RIFF file to a RIFX file
wav.toRIFX();

// Turn a RIFX file to a RIFF file
wav.toRIFF();

IMA-ADPCM

16-bit 8000 Hz mono wave files can be compressed as 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.

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:
wav.fromIMAADPCM();

Decoding always result in 16-bit audio. To decode to another bit depth:

// 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:

// 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:

// Decode 8-bit A-Law as 16-bit:
wav.fromALaw();

Decoding always result in 16-bit audio. To decode to another bit depth:

// 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:

// 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:

// Decode 8-bit mu-Law as 16-bit:
wav.fromMuLaw();

Decoding always result in 16-bit audio. To decode to another bit depth:

// 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.

// Load a wav file with 32-bit audio
let wav = new WaveFile(fs.readFileSync("32bit-file.wav"));

// Change the bit depth to 24-bit
wav.toBitDepth("24");

// Write the new 24-bit file
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.

// Load a wav file with 16kHz audio
let wav = new WaveFile(fs.readFileSync("16kHz-file.wav"));

// Change the sample rate to 44.1kHz
// using the default configuration
wav.toSampleRate(44100);
// this is the same as:
// wav.toSampleRate(44100, {method: "cubic"});

// Write the new 44.1kHz file
fs.writeFileSync("44100Hz-file.wav", wav.toBuffer());

To use another method:

// Change the sample rate to 44.1kHz using 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:

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

// Will use 'linear' method with LPF
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'});

// Will use 'linear' method with a IIR LPF, the default
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:

// convert the file to PCM
wav.fromMuLaw();
// resample
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:

// Load a wav file with no "bext"
let wav = new WaveFile(fs.readFileSync("32bit-file.wav"));

// Add some BWF metadata
wav.bext.originator = "wavefile";

// 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.

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:

/** @type {string} */
let iXMLValue = wav.getiXML();
/** @type {string} */
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.

// Both will return de-interleaved samples
samples = wav.getSamples();
samples = wav.getSamples(false);

// To get interleaved samples
samples = wav.getSamples(true);

You can use any typed array as the output of getSamples():

// Will return the samples de-interleaved,
// packed in a array of Int32Array objects, one for each channel
samples = wav.getSamples(false, Int32Array);
// will return the samples de-interleaved,
// packed in a array of Int16Array objects, one for each channel
let samples = getSamples(false, Int16Array);
// will return the samples interleaved, packed in a 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();

// some samples
let samples = [561, 1200, 423];

// Create a WaveFile using the samples
wav.fromScratch(1, 8000, "16", samples);

// Getting and setting a sample in the WaveFile instance:
wav.getSample(1); // return 1200, the value of the second sample
wav.setSample(1, 10); // change the second sample to 10
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
  • -2147483648 to 2147483647 for 32-bit
  • -1.0 to 1.0 for 32-bit (float)
  • -1.0 to 1.0 for 64-bit (float)

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.
               Use with --method to change the interpolation method:
               Ex: wavefile in.wav --resample=8000 --method=sinc out.wav
               If --method is ommited, cubic interpolation will be used.

  --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.

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.

API

To create a WaveFile object:

// Create a empty WaveFile object
WaveFile();

// Create a WaveFile object with the contents of a wav file buffer
WaveFile(wav);

/**
 * @param {Uint8Array=} wav A wave file buffer.
 * @throws {Error} If no "RIFF" chunk is found.
 * @throws {Error} If no "fmt " chunk is found.
 * @throws {Error} If no "data" chunk is found.
 */
WaveFile(wav);

The WaveFile methods

/**
 * Set up the WaveFileCreator object based on the arguments passed.
 * Existing chunks are reset.
 * @param {number} numChannels The number of channels.
 * @param {number} sampleRate The sample rate.
 *    Integers like 8000, 44100, 48000, 96000, 192000.
 * @param {string} bitDepthCode 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').
 * @param {!(Array|TypedArray)} samples The samples.
 * @param {Object=} options Optional. Used to force the container
 *    as RIFX with {'container': 'RIFX'}
 * @throws {Error} If any argument does not meet the criteria.
 */
WaveFile.fromScratch(numChannels, sampleRate, bitDepth, samples, options) {}

/**
 * Set up the WaveFileParser object from a 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.
 * @throws {Error} If format is not WAVE.
 * @throws {Error} If no 'fmt ' chunk is found.
 * @throws {Error} If no 'data' chunk is found.
 */
WaveFile.fromBuffer(bytes, samples=true) {}

/**
 * Return a byte buffer representig the WaveFile object as a .wav file.
 * The return value of this method can be written straight to disk.
 * @return {!Uint8Array} A .wav file.
 * @throws {Error} If any property of the object appears invalid.
 */
WaveFile.toBuffer() {}

/**
 * Use a .wav file encoded as a base64 string to load the WaveFile object.
 * @param {string} base64String A .wav file as a base64 string.
 * @throws {Error} If any property of the object appears invalid.
 */
WaveFile.fromBase64(base64String) {}

/**
 * Return a base64 string representig the WaveFile object as a .wav file.
 * @return {string} A .wav file as a base64 string.
 * @throws {Error} If any property of the object appears invalid.
 */
WaveFile.toBase64() {}

/**
 * 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 {string} A .wav file as a DataURI.
 * @throws {Error} If any property of the object appears invalid.
 */
WaveFile.toDataURI() {}

/**
 * Use a .wav file encoded as a DataURI to load the WaveFile object.
 * @param {string} dataURI A .wav file as DataURI.
 * @throws {Error} If any property of the object appears invalid.
 */
WaveFile.fromDataURI(dataURI) {}

/**
 * Force a file as RIFF.
 */
WaveFile.toRIFF() {}

/**
 * Force a file as RIFX.
 */
WaveFile.toRIFX() {}

/**
 * Change the bit depth of the samples.
 * @param {string} newBitDepth The new bit depth of the samples.
 *    One of '8' ... '32' (integers), '32f' or '64' (floats)
 * @param {boolean=} [changeResolution=true] A boolean indicating if the
 *    resolution of samples should be actually changed or not.
 * @throws {Error} If the bit depth is not valid.
 */
WaveFile.toBitDepth(bitDepth, changeResolution=true) {}

/**
 * Convert the sample rate of the file.
 * @param {number} sampleRate The target sample rate.
 * @param {Object=} options The extra configuration, if needed.
 */
WaveFile.toSampleRate(sampleRate, options=null) {};

/**
 * Encode a 16-bit wave file as 4-bit IMA ADPCM.
 * @throws {Error} If sample rate is not 8000.
 * @throws {Error} If number of channels is not 1.
 */
WaveFile.toIMAADPCM() {}

/**
 * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
 * @param {string=} [bitDepthCode='16'] The new bit depth of the samples.
 *    One of '8' ... '32' (integers), '32f' or '64' (floats).
 */
WaveFile.fromIMAADPCM(bitDepth='16') {}

/**
 * Encode 16-bit wave file as 8-bit A-Law.
 */
WaveFile.toALaw() {}

/**
 * Decode a 8-bit A-Law wave file into a 16-bit wave file.
 * @param {string=} [bitDepthCode='16'] The new bit depth of the samples.
 *    One of '8' ... '32' (integers), '32f' or '64' (floats).
 */
WaveFile.fromALaw(bitDepth='16') {}

/**
 * Encode 16-bit wave file as 8-bit mu-Law.
 */
WaveFile.toMuLaw() {}

/**
 * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
 * @param {string=} [bitDepthCode='16'] The new bit depth of the samples.
 *    One of '8' ... '32' (integers), '32f' or '64' (floats).
 */
WaveFile.fromMuLaw(bitDepth='16') {}

/**
 * Write a RIFF tag in the INFO chunk. If the tag do not exist,
 * then it is created. It if exists, it is overwritten.
 * @param {string} tag The tag name.
 * @param {string} value The tag value.
 * @throws {Error} If the tag name is not valid.
 */
WaveFile.setTag(tag, value) {}

/**
 * Return the value of a RIFF tag in the INFO chunk.
 * @param {string} tag The tag name.
 * @return {?string} The value if the tag is found, null otherwise.
 */
WaveFile.getTag(tag) {}

/**
 * Remove a RIFF tag in the INFO chunk.
 * @param {string} tag The tag name.
 * @return {boolean} True if a tag was deleted.
 */
WaveFile.deleteTag(tag) {}

/**
 * Return a Object<tag, value> with the RIFF tags in the file.
 * @return {!Object<string, string>} The file tags.
 */
WaveFile.listTags() {}

/**
 * Create a cue point in the wave file.
 * @param {!Object} pointData 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.dwCodePage
 * 
 * # This is what a complete pointData object look like:
 * {
 *   position: number,
 *   label: ?string,
 *   end: ?number,
 *   dwPurposeID: ?number,
 *   dwCountry: ?number,
 *   dwLanguage: ?number,
 *   dwDialect: ?number,
 *   dwCodePage: ?number
 * }
 */
WaveFile.setCuePoint(pointData) {}

/**
 * Remove a cue point from a wave file.
 * @param {number} index the index of the point. First is 1,
 *      second is 2, and so on.
 */
WaveFile.deleteCuePoint(index) {}

/**
 * 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, // the region length as a sample count
 *     dwPurposeID: 544106354,
 *     dwCountry: 0,
 *     dwLanguage: 0,
 *     dwDialect: 0,
 *     dwCodePage: 0,
 *   }
 * @return {!Array<Object>}
 */
WaveFile.listCuePoints() {}

/**
 * Update the label of a cue point.
 * @param {number} pointIndex The ID of the cue point.
 * @param {string} label The new text for the label.
 */
WaveFile.updateLabel(pointIndex, label) {}

/**
 * Return the samples packed in a Float64Array.
 * @param {boolean=} [interleaved=false] True to return interleaved samples,
 *   false to return the samples de-interleaved.
 * @param {Function=} [OutputObject=Float64Array] The sample container.
 * @return {!(Array|TypedArray)} the samples.
 */
WaveFile.getSamples(interleaved=false, OutputObject=Float64Array) {};

/**
 * Return the sample at a given index.
 * @param {number} index The sample index.
 * @return {number} The sample.
 * @throws {Error} If the sample index is off range.
 */
WaveFile.getSample(index) {};

/**
 * Set the sample at a given index.
 * @param {number} index The sample index.
 * @param {number} sample The sample.
 * @throws {Error} If the sample index is off range.
 */
WaveFile.setSample(index, sample) {};


/**
 * Return the value of the iXML chunk.
 * @return {string} The contents of the iXML chunk.
 */
WaveFile.getiXML() {};

/**
 * Set the value of the iXML chunk.
 * @param {string} iXMLValue The value for the iXML chunk.
 * @throws {TypeError} If the value is not a string.
 */
WaveFile.setiXML(iXMLValue) {};

/**
 * Get the value of the _PMX chunk.
 * @return {string} The contents of the _PMX chunk.
 */
WaveFile.get_PMX() {};

/**
 * Set the value of the _PMX chunk.
 * @param {string} _PMXValue The value for the _PMX chunk.
 * @throws {TypeError} If the value is not a string.
 */
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, // 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, // the region length as a sample count
    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

/**
 * The container identifier.
 * "RIFF", "RIFX" and "RF64" are supported.
 * @type {string}
 */
WaveFile.container = '';
/**
 * @type {number}
 */
WaveFile.chunkSize = 0;
/**
 * The format.
 * Always 'WAVE'.
 * @type {string}
 */
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: []
};
/**
 * The data of the "fact" chunk.
 * @type {!Object<string, *>}
 */
WaveFile.fact = {
    /** @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: [],
};
/**
 * 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: [],
};
/**
 * 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
};
/**
 * The data of the 'iXML' chunk.
 * @type {!Object<string, *>}
 */
WaveFile.iXML = {
  /** @type {string} */
  chunkId: '',
  /** @type {number} */
  chunkSize: 0,
  /** @type {string} */
  value: ''
};
/**
 * The data of the "ds64" chunk.
 * Used only with RF64 files.
 * @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": []
};
/**
 * The data of the "data" chunk.
 * @type {!Object<string, *>}
 */
WaveFile.data = {
    /** @type {string} */
    chunkId: '',
    /** @type {number} */
    chunkSize: 0,
    /** @type {!Uint8Array} */
    samples: new Uint8Array(0)
};
/**
 * The data of the "LIST" chunks.
 * Each item in this list look like this:
 *  {
 *      chunkId: '',
 *      chunkSize: 0,
 *      format: '',
 *      subChunks: []
 *   }
 * @type {!Array<!Object>}
 */
WaveFile.LIST = [];
/**
 * The data of the "junk" chunk.
 * @type {!Object<string, *>}
 */
WaveFile.junk = {
    /** @type {string} */
    chunkId: '',
    /** @type {number} */
    chunkSize: 0,
    /** @type {!Array<number>} */
    chunkData: []
};
/**
 * The data of the '_PMX' chunk.
 * @type {!Object<string, *>}
 */
WaveFile._PMX = {
  /** @type {string} */
  chunkId: '',
  /** @type {number} */
  chunkSize: 0,
  /** @type {string} */
  value: ''
};
/**
 * The bit depth code according to the samples.
 * @type {string}
 */
WaveFile.bitDepth =  '';

Cue points

Items in cue.points are objects like this:

{
    /** @type {number} */
    dwName: 0, // a cue point ID
    /** @type {number} */
    dwPosition: 0,
    /** @type {number} */
    fccChunk: 0,
    /** @type {number} */
    dwChunkStart: 0,
    /** @type {number} */
    dwBlockStart: 0,
    /** @type {number} */
    dwSampleOffset: 0
}

Sample loops

Items in smpl.loops are objects like this:

{
    /** @type {string} */
    dwName: '', // a cue point ID
    /** @type {number} */
    dwType: 0,
    /** @type {number} */
    dwStart: 0,
    /** @type {number} */
    dwEnd: 0,
    /** @type {number} */
    dwFraction: 0,
    /** @type {number} */
    dwPlayCount: 0
}

LIST chunk

"LIST" chunk data is stored as follows:

/**
 * An array of the "LIST" chunks present in the file.
 * @type {!Array<!Object>}
 */
WaveFile.LIST = [];

Items in WaveFile.LIST are objects like this:

{
    /** @type {string} */
    chunkId: '', // always 'LIST'
    /** @type {number} */
    chunkSize: 0,
    /** @type {string} */
    format: '', // 'adtl' or 'INFO'
    /** @type {!Array<!Object>} */
    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:

{
    /** @type {string} */
    chunkId: '', // some RIFF tag
    /** @type {number} */
    chunkSize 0,
    /** @type {string} */
    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

FOSSA Status

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.

wavefile's People

Contributors

rochars avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

wavefile's Issues

npm run build failing

Hi! Today, I made a fresh fork of wavefile and then executed...

git clone [email protected]:erikh2000/wavefile.git
cd wavefile
npm install
npm run build

The build fails with several error messages along these lines (can post the full spiel, if you like):

../../node_modules/@types/jest/index.d.ts:458:62 - error TS1005: ']' expected.

458     type NonFunctionPropertyNames<T> = keyof { [K in keyof T as T[K] extends Func ? never : K]: T[K] };

../../node_modules/@types/jest/index.d.ts:458:65 - error TS1005: ';' expected.

458     type NonFunctionPropertyNames<T> = keyof { [K in keyof T as T[K] extends Func ? never : K]: T[K] };
                                                                    ~

../../node_modules/@types/jest/index.d.ts:458:70 - error TS1005: ';' expected.

458     type NonFunctionPropertyNames<T> = keyof { [K in keyof T as T[K] extends Func ? never : K]: T[K] };
                                                                         ~~~~~~~

../../node_modules/@types/jest/index.d.ts:458:94 - error TS1005: ';' expected.

458     type NonFunctionPropertyNames<T> = keyof { [K in keyof T as T[K] extends Func ? never : K]: T[K] };
                                                                                                 ~

../../node_modules/@types/jest/index.d.ts:458:95 - error TS1128: Declaration or statement expected.

458     type NonFunctionPropertyNames<T> = keyof { [K in keyof T as T[K] extends Func ? never : K]: T[K] };

This output is coming more specifically from executing the tsc ./test/TypeScript/index.ts command in the test-tsc package.json script:

No preceding commands, e.g. rollup -c, are outputting failure messages.

I promise I didn't change anything in my fork. Any ideas on how to fix? Maybe there is an uncommitted file on somebody's local directory, e.g. tsconfig.json? Or a global NPM package that is relied upon? Or I'm building with the wrong version of node or npm?

Recognize End Points for Cue Markers

This is a feature request or enhancement, but I'm wondering if it is possible for wavefile to recognize cue markers that have both a start AND end point—markers like this can be created in Adobe Audition, for example. Note the "start" and "end" times in this screenshot:

Screen Shot 2020-01-02 at 3 14 14 PM

And here is a link to the file that the screenshot was taken with:

https://www.dropbox.com/transfer/AAAAACWjc7db0I3xtIeZ8l9CdhEH-kgKOCyCfrkJFS1nwrv-vYQ3Nu8

Would love to know your thoughts on if this is possible.

Stereo to Mono and Mono to Stereo

How can wavefile be used to change this?
This is currently an important topic for speech recognition as data is often recorded as stereo, but needs to be submitted for recognition as mono.

Thanks!

mpeg and cart support?

Hello, I work in public radio here in the US, and we use 2 additional standards on top of BWF, with wav files containing MPEG data chunks, and also a cart chunk which gives additional information for broadcast scheduling and automation systems.

MPEG support (compression code 80, mext chunk, and extensions to fmt)
https://tech.ebu.ch/docs/tech/tech3285s1.pdf

Cart Chunk
http://www.aes.org/publications/standards/search.cfm?docID=41
(I have purchased the spec, but b/c of rights and licensing I will not post it)
http://www.cartchunk.org/cc_spec.htm (most of the spec is here)

I'm interested in adding support for these, would you be open to such additions to your project?
Happy to work on some PRs for these.

FWIW - I have experience implementing support for these in Ruby, https://github.com/kookster/nu_wav, and I've used that gem to parse and create such wav files in production systems, but rather than port that to js, I'd rather enhance and support your high quality work on wav files.

Can I make new WaveFile(...) from browser?

Hello!

I have only a link to file on remote server. How can I make WaveFile from browser?

var WaveFile = new WaveFile('./trueWav.wav');
console.log(wav.chunkSize);

returns Uncaught Error: Not a supported format.

How can I make a buffer from it in browser?

Thank you :)

CUE: Marker & Region

Hi,

In REAPER, is seems that there is two type of elements marked by CUE points, markers, and regions.

It seems that wavefile is only able to get marker and region start, not region end.

Screenshot

PS: note that pointes are simply named with an index, contrary to what I can see in REAPER. But this is surely another issue.

Thanks again for this library !

Use with Node.js and huge Wave File (490MB) Heap Out of Memory

Hi,

I finally found this library. I was looking for something like it for a long time. But sadly it appears to have pretty strong limitations (or I have not found the documentation for my problem):

In my scenario, I want to use the library to update the CUE points within a node.js local processed script. The files I am editing are podcasts that are 1 hour long - thus the WAV files tend to be around 500MB. The library seems to only handle smaller files (I am getting the JavaScript heap out of memory with the big file, but not with a 1-minute file that's only 15MB in size).

So my question is two-fold:

  1. Is the library not able to handle the big files at all? Is it a limitation of Node.JS or the library? Or am I missing something?
  2. If the library is not able to handle big files by design, is there documentation of this limitation? I have not found one.

I'd really love to be able to use this lib. It's an amazing project and it would be a lifesaver for me. Sadly right now I can't really make much use of it.

Looking forward to working with WaveFile!

Chris

P.S.: I also tried setting the node --max-old-space-size=8192 to ridiculously high values. Nothing worked.

Writing BWF Bext chunk

Hi,

first of thanks for creating this <3! I was wondering if it would be possible to add support to write/update the bwf bext chunk of a wav file. I tried changing the values in .bext but it's not written back to the file. I've checked with bwf meta edit, a tool to edit bwf meta data.

Thanks,
Michael

Unicode text is not read correctly

I have a WAVE file with cue points. One of them has the label "Marker 01 abcäöüß". Yet when I call listCuePoints(), the corresponding label string is "Marker 01 abcä�ö�ü�ß�".

The reason for this is that the method WaveFile.readZSTR_ tries to convert the string byte by byte, which gives wrong results for multi-byte UTF-8 code points.

I found an easy fix. Given that this project is currently unmaintained, I'm not opening a PR. Instead, here's a small snippet of code that patches the bug at runtime:

const WaveFile = require('wavefile');
const { unpackString } = require('byte-data');

WaveFile.prototype.readZSTR_ = function patchedReadZSTR_(bytes, start = 0) {
  let end = start;
  while (end < bytes.length && bytes[end]) end++;

  return unpackString(bytes, start, end);
}

byteData_ is empty object

Hi I use this in my react native project. When I try to load wav file from data no matter base64 or a buffer, I always got error that byteData_ is an empty so things like byteData_.types is undefined. I had to change all other byteData_ to byteData in file except the definition one here. I also had to do the same thing for riffChunks_. Can someone help me with this? I don't quite get why byteData without the underscore has all the information. Thanks

Compressing and converting wav chunks

I am using an API that streams chunks of audio as they become available. I need to send this to Twilio which only accepts mulaw encoded base64. Is there a way to convert each chunk individually to mulaw bas64 or must it be done only when the full file is received?

In Firefox and Safari => Error: Not a supported format

Thanks for the great tool. I am using it to read the selected wave file before uploading it.
It works well in Chrome but not in Firefox and Safari. Following is the code snippet.

const wav = new WaveFile();
var reader = new FileReader();
reader.readAsDataURL(file);

reader.onload = function() {
    wav.fromDataURI(reader.result);
    //...
}

Upon checking, I found that the container value is a blank string in Firefox while 'RIFF' in Chrome.

this.container = this.readString(buffer, 4);

if (this.supported_containers.indexOf(this.container) === -1) {
  throw Error('Not a supported format.');
}

PS - I hope you are safe and healthy in these crazy times.

use explicit path in imports

true ESM requires path to be explicit in order to find them. remote path resolver like http import isn't so good at locating this extension-less paths cuz it can't scan the dir if you meant ./foo/index.js or ./foo.js

this would allow to run the code without having to even compile it first.

Semver adherence

I think the recent updates didn't have to be major version updates. It seems like there aren't any breaking api changes but just bug fixes and added functionality. So this could have been done with minor and patch versions. See the semver spec for information on what version number to increment when: https://semver.org

It would be nice to also document the releases in something like a changelog: https://keepachangelog.com/en/1.0.0, so people know what changed.

Not a supported format error for 32f wav

With the latest version I'm getting a Not a supported format. error when attempting to read a 32f, mono wav.

Build log here
Code that's failing here
Code for failing test here

I didn't see any tests for reading 32f wavs, is this currently supposed to be functional?

How are you @rochars?

Hey @rochars I just noticed you pushed a commit a few days ago, and I hope it means you are ok.
If there is anything I can do to help, with this project or whatever I can do, please let me know.

  • Andrew

Bug in read of RIFF signature..?

WAV with RIFF chunks like:

image

is read by wavefile with a funky signature like:

{
  "chunkId": "RIFF",
  "chunkSize": 639470,
  "format": "WAVE",
  "subChunks": [
    {
      "chunkId": "fmt ",
      "chunkSize": 16,
      "chunkData": {
        "start": 20,
        "end": 36
      }
    },
    {
      "chunkId": "data",
      "chunkSize": 639096,
      "chunkData": {
        "start": 44,
        "end": 639140
      }
    },
    {
      "chunkId": "LIST",
      "chunkSize": 68,
      "format": "INFO",
      "subChunks": [
        {
          "chunkId": "INAM",
          "chunkSize": 14,
          "chunkData": {
            "start": 639160,
            "end": 639174
          }
        },
        {
          "chunkId": "ISFT",
          "chunkSize": 34,
          "chunkData": {
            "start": 639182,
            "end": 639216
          }
        },
        {
          "chunkId": "cue ",
          "chunkSize": 76,
          "chunkData": {
            "start": 639224,
            "end": 639300
          }
        },
        {
          "chunkId": "LIST",
          "chunkSize": 102,
          "format": "adtl",
          "subChunks": [
            {
              "chunkId": "ltxt",
              "chunkSize": 20,
              "chunkData": {
                "start": 639320,
                "end": 639340
              }
            },
            {
              "chunkId": "labl",
              "chunkSize": 16,
              "chunkData": {
                "start": 639348,
                "end": 639364
              }
            },
            {
              "chunkId": "labl",
              "chunkSize": 16,
              "chunkData": {
                "start": 639372,
                "end": 639388
              }
            },
            {
              "chunkId": "labl",
              "chunkSize": 14,
              "chunkData": {
                "start": 639396,
                "end": 639410
              }
            },
            {
              "chunkId": "smpl",
              "chunkSize": 60,
              "chunkData": {
                "start": 639418,
                "end": 639478
              }
            }
          ]
        },
        {
          "chunkId": "smpl",
          "chunkSize": 60,
          "chunkData": {
            "start": 639418,
            "end": 639478
          }
        }
      ]
    },
    {
      "chunkId": "cue ",
      "chunkSize": 76,
      "chunkData": {
        "start": 639224,
        "end": 639300
      }
    },
    {
      "chunkId": "LIST",
      "chunkSize": 102,
      "format": "adtl",
      "subChunks": [
        {
          "chunkId": "ltxt",
          "chunkSize": 20,
          "chunkData": {
            "start": 639320,
            "end": 639340
          }
        },
        {
          "chunkId": "labl",
          "chunkSize": 16,
          "chunkData": {
            "start": 639348,
            "end": 639364
          }
        },
        {
          "chunkId": "labl",
          "chunkSize": 16,
          "chunkData": {
            "start": 639372,
            "end": 639388
          }
        },
        {
          "chunkId": "labl",
          "chunkSize": 14,
          "chunkData": {
            "start": 639396,
            "end": 639410
          }
        },
        {
          "chunkId": "smpl",
          "chunkSize": 60,
          "chunkData": {
            "start": 639418,
            "end": 639478
          }
        }
      ]
    },
    {
      "chunkId": "smpl",
      "chunkSize": 60,
      "chunkData": {
        "start": 639418,
        "end": 639478
      }
    }
  ]
}

This causes an exception where wavefile makes one LIST type (INFO or adtl) a subchunk of the other LIST type.

Do less things: remove base64 stuff

You got a couple of ways to load wav files...

  • fromBase64
  • fromDataURI
  • fromBuffer

and also exporting them:

  • toBuffer
  • toBase64
  • toDataURI

Base64 isn't a good container cuz it takes up way more space, a Uint8Array is way better.

  • if the intention is to play something in a <audio src=""> element then it's better to create a Object URL from a blob
  • if the intention is to upload stuff via a api using json, then don't there are better ways to send of binary data + json

encourage base64 today isn't so good.
it's better to just have one way to solve it so that less code can be shipped and bundled.

also don't know how i feel about the name toBuffer and fromBuffer the name mostly implies that it is dealing with NodeJS buffers. how about to/fromUint8array ?

distortion in sound from using wav.fromIMAADPCM()

wav file from telephony system recorded using 8 bit mono, 8000Hz, u-law, The voicemail plays fine in VLC and other media player. When decode it using fromIMAADPCM(), static noise, choppy playback got introduced.
When running command line for bit depth: the wav file returns 505

Any suggestions on fixing it?

ES6 import syntax results in TypeError: wavefile_1.default is not a constructor

Importing WaveFile like so: import WaveFile from "wavefile" and attempting to create an instance like: const reader = new WaveFile() results in the following error: TypeError: wavefile_1.default is not a constructor.

However, importing like: const wavefile = require("wavefile") works fine, but then TypeScript type checking stops working.

`samples` does not exist

It seems that samples does not exist as documented in the readme:

// array of numbers
console.log(wav.samples);

instead, it's available under samples_, and the unit tests seem to only test for the samples_ property as well. Is this an oversight? Should samples not be available under samples and not samples_?

getSamples() has wrong return type declaration

This is how currently getSamples() is defined in index.d.ts
getSamples(interleaved?:boolean, OutputObject?: Function): Float64Array;

It is supposed to return a Float64Array. But, according to the docs, when there are more than 2 channels, it returns an array of Float64Arrays.

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.

This creates confusion when you try to create some logic around it, for example, if you need to go through all channels, you might write something like this:

const numChannels = (wave.fmt as any).numChannels;
let sampleChannels: Float64Array[] = [];

if (numChannels > 1) {
    sampleChannels = wave.getSamples() as Float64Array[];
}
else {
    sampleChannels.push(wave.getSamples() as Float64Array);
}

Unfortunately, this fails. I had to change the return type in index.d.ts to: Float64Array | Float64Array[].

I think this would be the right declaration and should be changed, or the function could be changed so that it always returns an array of arrays.

waveFile.toBuffer as 16 bit array?

I'm just working on something that generates peaks for wave files which I read the data from via base64, and to match what I have I'd prefer the buffer to output in 16 bit, possibly signed format?

Is there a way to do this with what

This would be my ideal:

const waveFile = new WaveFile();      
waveFile.fromDataURI(waveFileBase64);
const audioFileBuffer = waveFile.toBuffer('16');

Or can I just convert this to 16 bit?

Many thanks

mulaw extensible riff fmt header

I can't find any source stating that mu-law should require an extensible fmt header to to include the validbitspersample. including this in the header breaks several players, including windows media player and groove music. ffmpeg and soundforge do not put this extensible header in.

Is there some reason why it should be there?

Create wav file from socket buffer

Expected Behavior

I'm trying to receive the sound (buffer) recording from a web (javascript) with socket.io in the server and then create the wav file using your library.

Current Behavior

There is no error but when the created wav file is reproduced (VLC), it is like static or illegible sounds

Steps to Reproduce

  1. Send the audio as buffer with sockets from html/javascript client
scriptNode.onaudioprocess = function (audioEvent) {
    if (recording) {
        input = audioEvent.inputBuffer.getChannelData(0);

        // convert float audio data to 16-bit PCM
        var buffer = new ArrayBuffer(input.length * 2)
        var output = new DataView(buffer);
        for (var i = 0, offset = 0; i < input.length; i++, offset += 2) {
            var s = Math.max(-1, Math.min(1, input[i]));
            output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
        }
        socketio.emit('write-audio', buffer);
    }
}
  1. Receive the sound from javascript into the socket.io server with nodejs
//just initialize an uuid
@SocketIoEvent(eventName = "start-recording")
this.startRecording = async (message, currentSocket, globalSocket) => {
  console.log(message);
  currentSocket["current_wav_id"] = uuidv4();
}  

//join the chunks/buffer into one
@SocketIoEvent(eventName = "write-audio")
this.writeAudo = async (message, currentSocket, globalSocket) => {
  console.log(message);
  console.log(currentSocket["current_wav_id"])

  if(typeof buffers[currentSocket["current_wav_id"]]  === 'undefined'){
    buffers[currentSocket["current_wav_id"]] = message;
  }else{
    var newBuffer = Buffer.concat([buffers[currentSocket["current_wav_id"]], message]);
    buffers[currentSocket["current_wav_id"]] = newBuffer;
  }
}  

//save the buffer as wav file
@SocketIoEvent(eventName = "end-recording")
this.endRecording = async (message, currentSocket, globalSocket) => {
  console.log(message);
  console.log(currentSocket["current_wav_id"])
  console.log(buffers[currentSocket["current_wav_id"]]);
  var wav = new WaveFile();
  wav.fromScratch(1, 44100, '16', buffers[currentSocket["current_wav_id"]]);  
  fs.promises.writeFile(`/tmp/sandbox/${currentSocket["current_wav_id"]}.wav`, wav.toBuffer());   
} 

Context (Environment)

  • Ubuntu 22
  • Node 16.20.2
  • wavfile 11.0.0

Additional

The html client is sending a valid waf file because it works with python: the write-audio event is received perfectly and the wav is created

Html client

The html client is this https://github.com/miguelgrinberg/socketio-examples/blob/8281e127cf0d2228e793594527d7d19e8138a62e/audio/static/audio/main.js#L145C42-L145C48

server

https://github.com/miguelgrinberg/socketio-examples/blob/8281e127cf0d2228e793594527d7d19e8138a62e/audio/audio.py#L23

Reproducible sample

I will try to create a Minimal, Reproducible Example.

Thanks in advance

Issue converting Twilio Media Stream (mulaw) to PCM for @aws-sdk/client-transcribe-streaming

Hello, and thanks for this library! I'm trying to use it to convert audio coming in from Twilio so it can be transcribed by Amazon Transcribe. Unfortunately, I'm getting back empty responses from AWS (ex: { TranscriptEvent: { Transcript: { Results: [] } } }). Here's the transform stream I'm using for the conversion.

new stream.Transform({
  transform(chunk, encoding, done) {
    const wav = new WaveFile();
    wav.fromScratch(1, 8000, '8m', Buffer.from(chunk, 'base64'));
    wav.fromMuLaw();
    this.push(Buffer.from(wav.data.samples));
    done();
  }
});

The audio from Twilio is mulaw encoded. AWS has this on their best practices webpage for supported audio "PCM (only signed 16-bit little-endian audio formats, which does not include WAV)". I'm assuming that's exactly what I get when running wav.fromMuLaw(), correct? Should I try using wav.getSamples(false, Int16Array) instead of wav.data.samples? Any other things I should try?

Thanks for your help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.