node-wav
Node.js streams for reading and writing Microsoft WAVE audio files, with automatic header parsing and correction.
Repository Health
Technical Analysis
wav (node-wav) provides Reader and Writer Node.js stream classes for working with Microsoft WAVE audio files. The Reader consumes a WAV file stream, parses the RIFF/WAVE header (including big-endian RIFX variants), and emits a format event with sample rate, channel count, bit depth, and encoding details before passing through the raw PCM (or IEEE float, A-law, mu-law) audio data with the header stripped.
The Writer class does the inverse: it accepts raw PCM data written to it and prepends a valid 44-byte WAVE header, initially with a placeholder data-length since the total size isn’t known upfront. A FileWriter subclass builds on Writer to handle the common case of writing directly to disk, reopening the file after the stream ends to patch in the correct byte-length values. Both classes are built on Node’s Transform stream API via the stream-parser mixin, making them composable with any other stream-based audio pipeline, such as piping decoded audio straight to speaker for playback.
What You Get
- A
Readerstream that parses WAVE headers and emits aformatevent with channels, sample rate, bit depth, and encoding - A
Writerstream that prepends a valid WAVE header to raw PCM data written to it - A
FileWritersubclass that automatically patches the correct byte-length into the header after writing to disk - Support for both RIFF (little-endian) and RIFX (big-endian) WAVE files, plus PCM, IEEE float, A-law, and mu-law formats
Common Use Cases
- Streaming WAV audio playback by piping a Reader into an output device like
speaker - Converting raw PCM buffers (e.g. from a microphone via
mic) into a valid.wavfile with FileWriter - Inspecting WAV file headers (sample rate, channels, bit depth) for audio tooling or format validation
- Building audio processing pipelines that need a WAVE codec layer without a native/binary dependency
Under The Hood
Architecture
node-wav is a small, modular library: index.js re-exports three independently requireable classes from lib/. Reader (lib/reader.js) extends Node’s Transform stream and mixes in stream-parser to declaratively consume fixed-size byte chunks (_bytes/_passthrough), driving a chained state machine of handler methods (_onRiffID -> _onChunkSize -> _onFormat -> _onSubchunk1ID -> _onSubchunk1Size -> _onSubchunk1 -> _onSubchunk2ID -> data/fact/unknown branches) that emits format once the header is parsed and then passes through raw audio bytes. Writer (lib/writer.js) is a simpler Transform that precomputes a 44-byte header in its constructor, pushes it immediately, passes through written PCM chunks while tracking bytesProcessed, and on _flush emits a header event with the corrected byte-length fields patched in. FileWriter (lib/file-writer.js) composes Writer with an fs.WriteStream via pipe(), then reacts to the header event by reopening the file with fs.open/fs.write/fs.close to patch the header bytes at offset 0 - a real coupling between stream completion and synchronous file-descriptor manipulation. Nothing shares a base class beyond Transform, so the three classes can be modified independently.
Tech Stack
The runtime dependency list reflects an older Node.js compatibility target: readable-stream (a userland Transform stream shim), stream-parser (the mixin providing _bytes/_passthrough chunked-read helpers used by Reader), buffer-alloc/buffer-from (safe-buffer-era polyfills predating native Buffer.alloc/Buffer.from), and debug for namespaced logging (wave:reader, wave:writer). There is no build step - plain CommonJS require/module.exports, no TypeScript, no bundler. Dev dependencies are mocha for tests and semistandard for linting, both run via npm test; CI is configured through .travis.yml and appveyor.yml for cross-platform (Linux/Windows) coverage. There is no bin field in package.json, so the package is consumed purely as an importable module.
Code Quality
Tests (test/reader.js) use Mocha with Node’s built-in assert, covering both RIFF (little-endian) and RIFX (big-endian) fixture files across multiple bit depths and formats (8-bit unsigned PCM, 16-bit signed PCM, 32/64-bit float), verifying the emitted format event fields and stream end events against binary fixtures in test/fixtures/. There is no dedicated test coverage for Writer or FileWriter - only Reader is exercised. Error handling in Reader emits descriptive error events for malformed headers (bad chunk ID, bad format, bad fmt id) rather than throwing; Writer/FileWriter propagate filesystem errors the same way. There is no TypeScript or runtime type checking - plain JS with JSDoc-style comments throughout. Linting is enforced via semistandard as part of the test script, and naming is consistent (an _onXxx handler convention with _-prefixed private methods).
What Makes It Unique
RIFF/WAVE parsing itself is a long-established, well-documented format, so node-wav’s contribution isn’t a new technique but a stream-idiomatic implementation of one: modeling both reading and writing as standard Node.js Transform streams (rather than a one-shot buffer-in/buffer-out parser) lets it compose directly into pipelines such as file.pipe(reader).pipe(speaker). It also handles live/unknown-length writes via a placeholder-then-patch header technique, and covers less-common cases like big-endian RIFX files, optional fact chunks, and forwarding unrecognized chunks as a chunk event instead of erroring outright. These are practical, considered extensions of a standard format rather than a fundamentally new approach.