No sound card driver. No audio library. Just the CPU and 44100 numbers a second.
No libc. No external calls. Sound is a list of numbers — generate the right ones, write them to a .wav, and the speaker moves. That's the whole trick.
- x86_64, Linux, NASM
- no libc, no external calls —
syscallor nothing - every sample computed by hand
- if it screeches, it builds character
make # binaries land in bin/The target is Linux, so run it in a container. Any OCI runtime works — Docker, Podman, Colima, OrbStack — they all read the same Dockerfile:
docker build -t asmfm .
docker run --rm -it -v "$PWD":/asmfm asmfm
make # inside the containerWhat I actually use: OrbStack + VS Code's Dev Containers extension. "Reopen in Container" and the terminal drops straight into Linux with make ready.
Note: sample buffers live in .data, not .bss — Rosetta's x86 translation chokes on pure-bss load segments (rosetta error: bss_size overflow).
./bin/beep # a single square wave. the "hello world" of sound
./bin/beep > out.wav # ...or dump it to a file and play it anywhere
aplay out.wav # (or open it in anything — it's a real WAV)Everything writes a valid RIFF/WAVE stream: 16-bit PCM, mono, 44.1 kHz. Pipe it, play it, keep it.
A speaker is a cone that moves in and out. Describe its position 44100 times a second and you've described a sound.
- Oscillators — square, sawtooth, triangle, and noise, generated one sample at a time. The square wave is the whole 8-bit soul: high for half a period, low for the other half.
- Pitch — a note is just a frequency. A table maps
A4 -> 440 Hz, and the rest of the notes fall out from there. - Sequencing — a tune is a list of
{ note, duration }. The player walks it and fills the buffer. - Output — a hand-written 44-byte WAV header, then the raw samples.
write(1, ...)and you're done.
The story so far — from a single beep to a real synth:
- a single beep (square wave -> WAV header ->
write) - one octave, in tune (note -> frequency table)
- a real melody, sequenced by hand
- all four oscillators (square, saw, triangle, noise for drums)
- polyphony — melody + bass + percussion at once (a tiny tracker)
- polyphony + ADSR — the mix, now without clicks
- ADSR envelopes, so notes breathe instead of clicking
- [AMBITIOUS] an FM synth — the "fm" was never just about radio
- vibrato — the pitch, wavering gently, for a voice that's alive
- tremolo — the volume, pulsing, like a heartbeat under the note
- strong tremolo — depth cranked up, the note pulsing rhythmically
- configurable tempo (BPM) — the same tune, fast or slow
- PWM — pulse-width modulation, the square wave that shivers
- a delay/echo — the sound, coming back to you, fading each time
- a low-pass filter — carve the highs, warm and mellow
- bitcrusher — crush the resolution, lo-fi and crunchy
- ring modulation — two signals multiplied, metallic and strange
- a resonant filter sweep — the "wah" that opens and closes
- distortion / overdrive — push the signal past its limits, crunchy and hot
- an auto-wah — a filter that follows the signal's own energy, funky and alive
- chorus — one voice becoming many, wide and shimmering
- reverb — a room built from math, so notes have somewhere to ring
- a flanger — a swept short delay, the sound of a jet passing overhead
- a phaser — moving notches drifting through the spectrum, swirling and psychedelic
- shimmer reverb — reflections rising into the light, ambient and celestial
- a stutter/glitch effect — sound shattered and repeated, hyperpop in assembly
- sidechain pumping — the volume breathing in time, the heartbeat of modern electronic music
- a compressor — taming the loud and lifting the quiet, the invisible hand of every mix
- a limiter — a hard ceiling, so nothing ever clips
- an arpeggiator — chords played one note at a time, automatically
- a step sequencer — a grid of on and off, the heart of every drum machine
- drum patterns — a real beatmaker, kick and snare and hat in pure asm
- swing / groove — nudging the offbeats late, so the machine feels human
- named chords — play Cmaj7 or Dm by name, not by hand-stacked frequencies
- microtonal tunings — stepping outside the 12 notes the West agreed on
- a text score format — compose in a file, no recompiling
- wavetable synthesis — a table of shapes, morphing as it plays (the modern sound, in pure asm)
- a pitch shifter — moving the pitch without moving the time (or the reverse)
- a vocoder — one signal's voice wrapped around another, the robot that sings
- [AMBITIOUS] multi-operator FM (6 operators, DX7-style algorithms)
- [AMBITIOUS] Karplus-Strong — plucked strings from a burst of noise
- [AMBITIOUS] granular synthesis — sound broken into a thousand grains, clouds of texture
- [AMBITIOUS] convolution reverb — the fingerprint of a real hall, not a mathematical guess
- [AMBITIOUS] a terminal waveform visualizer, drawing the sound as it plays
- [UNREASONABLE] spectral processing — an FFT in pure assembly, painting sound in frequencies
- [UNREASONABLE] real-time output straight to the sound card (/dev/dsp, then ALSA)
Little side-by-side tests, for hearing what a single feature actually does:
melody_hardvsmelody_smooth— the same solo melody with hard note edges vs a strong ADSR envelope. Play them back to back: one clicks, the other breathes.polyphony_adsr— polyphony and envelopes combined: the three-voice mix with per-note ADSR, so nothing clicks.
Because I'd made the CPU count, sort, hash, and draw — but never sing.
- the WAV/RIFF spec (it's mercifully short)
- the equal-temperament formula (twelve notes, one ratio)
- Intel® 64 and IA-32 manuals, still ~5000 pages, still bedtime reading
- a rubber duck with perfect pitch