A high-performance, lo-fi audio effect tool built from scratch in C++. This project demonstrates Systems Programming concepts including binary file manipulation, memory mapping, and digital signal processing (DSP) without relying on external audio libraries like libsndfile or JUCE.
Hear the effect in action. The tool reduces both the bit depth (resolution) and sample rate (frequency) to create a retro, robotic, or "broken" sound.
| Description | Audio File |
|---|---|
| Original Vocal (Clean 16-bit PCM) | |
| Bitcrushed (8-bit equivalent, 4x downsample) |
Note: Click the links above to play the WAV files directly in your browser.
- No Dependencies: Standard C++ Standard Library (
<vector>,<fstream>,<cstdint>) only. - Binary WAV Parsing: Custom implementation of the WAV file specification (RIFF/WAVE header parsing).
- Tunable Destruction:
- Bit Depth Reduction: Integer quantization simulating 8-bit, 4-bit, or even 1-bit audio.
- Sample Rate Decimation: Sample-and-hold algorithm to introduce metallic aliasing artifacts.
- High Performance: Direct memory manipulation of PCM data.
- C++ Compiler (GCC/MinGW)
- Make (or mingw32-make on Windows)
make(On Windows with MinGW, use mingw32-make)
./bitcrusher <input.wav> <output.wav> [crush_factor] [downsample_factor]- input.wav: Source PCM WAV file.
- output.wav: Destination file.
- crush_factor: Quantization intensity (default: 256). Higher = more distorted.
- downsample_factor: Sample rate reduction (default: 1). Higher = more robotic/metallic.
1. The "Retro Game" Sound (Recommended)
./bitcrusher samples/demo_original.wav samples/out_retro.wav 256 4Simulates ~8-bit hardware running at ~11kHz.
2. Modern Lo-Fi
./bitcrusher samples/demo_original.wav samples/out_lofi.wav 64 2Subtle digitization.
3. Total Destruction
./bitcrusher samples/demo_original.wav samples/out_broken.wav 1024 16Unintelligible digital noise.
Instead of using a library, we define the WAV header struct manually to match the binary layout of the WAV file format:
struct WavHeader {
char riff[4]; // "RIFF"
uint32_t overall_size; // File size
// ... exact 44-byte mapping ...
};We use std::ifstream in binary mode to map these bytes directly into memory.
The distortion is achieved through integer math. By defining a reductionFactor (e.g., 256), we perform integer division to truncate the signal's precision, then multiply back to scale it.
// Integer Division drops the remainder (Quantization)
sample = (sample / factor) * factor;To simulate a lower sample rate (which causes the characteristic "ringing"), we implement a Sample-and-Hold algorithm. We update the output signal only every N samples, holding the previous value in between.
if (i % downsample == 0) {
current_sample = input_data[i]; // Sample
}
output_data[i] = current_sample; // HoldThis project is open source and available under the MIT License.