Building an In-Browser Audio Converter: The Silent Resampling Trap in decodeAudioData
Standard Web Audio decodeAudioData quietly resamples any decoded file to the AudioContext rate with no indication in the output buffer. We explain how our converter probes container headers first to preserve original fidelity, why output is strictly WAV, and the limits of browser decoding.
Audio to WAV Converter
100% In-browser execution. Zero server uploads, instant results, free forever.
The Hidden Trap: Why decodeAudioData Quietly Alters Sample Rates
When engineers build in-browser audio tools using the HTML5 Web Audio API, the standard approach is straightforward: instantiate an AudioContext, pass an ArrayBuffer to decodeAudioData(), and manipulate the resulting AudioBuffer.
However, decodeAudioData() harbors a silent behavioral trap: it automatically resamples the decoded audio to the sample rate of the AudioContext it was invoked on, without any flag, warning, or record in the returned AudioBuffer.
For example, if a user loads a pristine 44,100 Hz recording into an AudioContext initialized on a system running at 48,000 Hz, decodeAudioData() silently resamples the entire recording to 48,000 Hz. Nothing in the returned object indicates that a sample rate conversion took place—the original 44.1 kHz rate is entirely erased. A naive web audio converter will quietly resample every file it processes, and then market the resulting output as "lossless".
To prevent this silent alteration, our converter architecture in /audio/convert separates inspection from decoding. In lib/tools/audio/probe.ts, our code reads the genuine native sample rate directly out of the file's container headers before invoking Web Audio: - For FLAC, it inspects the 20-bit sample rate field located in the STREAMINFO metadata block. - For AIFF, it extracts and decodes the 80-bit IEEE 754 extended float holding the sample frequency. - For MP4/M4A containers, it parses the size-then-type box hierarchy to find the audio sample entry. - For WAV, it reads the 32-bit sample rate field in the RIFF fmt chunk.
Once the source rate is extracted, an OfflineAudioContext is instantiated at that exact frequency. No resampling occurs unless the user explicitly requests a sample rate modification. Where a format change is structurally enforced by an underlying codec—such as Opus, which always decodes at 48,000 Hz regardless of the container rate—the UI explicitly flags the discrepancy rather than concealing it.
Format Decisions: Why Output Is Strictly WAV and Why We Do Not Ship an MP3 Encoder
A central design decision of /audio/convert is that it writes uncompressed WAV files and intentionally does not include an MP3 encoder.
This is not a temporary oversight; it is an architectural commitment: 1. Avoiding Lossy-to-Lossy Degradation: Re-encoding lossy source material (such as AAC, OGG Vorbis, or MP3) into another lossy MP3 format always introduces compounding quantization noise and generation loss. Converting to uncompressed linear PCM in a WAV container preserves every sample decoded by the browser. 2. No Bloated Third-Party Dependencies: Packaging an MP3 encoder would require shipping heavy WebAssembly binaries (often 1 MB or more) and reviewing complex patent and licensing restrictions against our MIT repository. 3. Universal Compatibility: WAV files with 16-bit or 24-bit PCM can be opened and edited across every digital audio workstation (DAW), operating system, and media player without codec negotiation.
The tool interface states this policy outright under the file selector, and our automated tests assert that this honest explanation remains present.
Browser Realities: Decoder Variance and Peak vs Loudness Normalization
Because /audio/convert uses the host browser's native media decoders, which audio files can be opened depends on the reader's browser runtime: - Format Discrepancies: Modern Chromium builds lacking proprietary system decoders may reject certain AAC or AIFF containers, whereas WebKit (Safari) natively handles them. To ensure consistent behavior, we implemented custom AIFF decoding logic in pure TypeScript. - Universal Baselines: Standard WAV and FLAC containers round-trip reliably across both Chromium and WebKit. - Peak vs Loudness Normalization: The tool offers peak normalization, which scales audio samples so the single loudest instant reaches a specified decibel ceiling (e.g. 0 dBFS or -1 dBFS). Peak normalization does not equal perceived loudness normalization (such as EBU R128 or ITU-R BS.1770 LUFS). Making two recordings sound equally loud requires psychoacoustic filtering and gating algorithms that we do not implement here.
The engine is covered by 74 unit tests across pure modules (19 in wav.test.ts, 22 in probe.test.ts, 33 in pcm.test.ts) and 12 browser e2e tests driving Chromium and WebKit. Memory boundaries are strictly enforced: input files are capped at 100 MB and output generation at 500 MB to prevent tab memory exhaustion.
Frequently Asked Questions (FAQ)
Why does decodeAudioData resample audio without notice?
The Web Audio API specification binds decodeAudioData to the destination AudioContext sampleRate to optimize playback through system hardware. For web audio synthesis this is convenient, but for file conversion it introduces silent resampling unless the context rate is deliberately configured to match the file headers.
Does peak normalization make all converted tracks sound equally loud?
No. Peak normalization matches the single highest amplitude peak to a ceiling. It does not measure or adjust perceived integrated loudness (LUFS).
Can I export an MP3 file using this converter?
No. The converter writes uncompressed WAV files only. We do not bundle an MP3 encoder, avoiding lossy re-encoding artifacts and third-party WebAssembly dependencies.
Related Guides & Solutions
What 'Lossless' Actually Means When You Cut an MP3
Ready to use Audio to WAV Converter?
Execute this workflow privately on your device right now without creating an account or paying for cloud API credits.