Building, running, controlling pots, and visualising output.
You need three things installed before anything works:
| tool | used for |
|---|---|
| gcc | Compiling convert.c and tests. Any recent version. |
| ffmpeg / ffplay | Decoding MP3 to raw PCM (ffmpeg) and playback (ffplay). |
| python3 + matplotlib + numpy | Only needed for visualize.py. Skip if you don't need it. |
# Debian / Ubuntu
sudo apt install gcc ffmpeg python3-matplotlib python3-numpy
# macOS (Homebrew)
brew install gcc ffmpeg
pip3 install matplotlib numpy
make generates four lookup tables first (log₂, pow₂, quarter sine, EQ ω₀),
then compiles convert.c. The lookup tables are Python-generated C headers
written into build/.
git clone https://github.com/dogxhood/audionoise.git
cd audionoise
make
The CFLAGS used for convert.o are worth noting:
# From the Makefile
convert.o: CFLAGS += -ffast-math -fsingle-precision-constant \
-Wfloat-conversion
-ffast-math enables associative floating-point reordering and
removes IEEE 754 edge-case handling (NaN, Inf).
-fsingle-precision-constant treats unadorned float literals as
float rather than double — important for a target
that has a single-precision FPU.
The convert binary reads raw s32le from stdin (or a file),
applies the named effect, and writes raw s32le to stdout (or a file).
The Makefile has targets for the common case:
# Convert MP3 to raw PCM (runs once; result is cached by make)
make input.raw
# Run the default echo effect, write to output.raw
make output.raw
# Run a specific effect directly, pipe to ffplay
./convert phaser input.raw | ffplay -v fatal -nodisp -autoexit \
-f s32le -ar 48000 -ch_layout mono -i pipe:0
# Other effects
./convert flanger input.raw | ffplay -f s32le -ar 48000 -ch_layout mono -i pipe:0
./convert boost input.raw | ffplay -f s32le -ar 48000 -ch_layout mono -i pipe:0
./convert echo input.raw | ffplay -f s32le -ar 48000 -ch_layout mono -i pipe:0
While the effect is running, you can change pot values in real time by writing 5-byte control packets to a named pipe. Open the pipe, send a packet per pot change:
# Packet format: 'p' + pot_index (0-3) + d1 + d2 + '\n'
# Value = 2*(d1*10+d2) - 100 → range -100..100 → mapped to pot range
# Example: set pot 0 to 50% (d1=5, d2=0 → value=0)
echo -n "p050 " > /tmp/audionoise_ctl
# From convert.c — the parsing loop
case 'p':
unsigned int idx = buf[1] - '0';
unsigned int d1 = buf[2] - '0';
unsigned int d2 = buf[3] - '0';
pots[idx] = 2*(d1*10+d2) - 100; // signed char, -100..100
describe(eff, pots);
break;
The describe() call prints the human-readable pot value to stdout
every time a pot changes — useful to see what the effect is actually doing
with a given numeric pot value.
visualize.py reads one or more raw s32le files and renders
an interactive waveform plot. It memory-maps the files via numpy and downsamples
to at most 5000 plot points — so it handles large files without running out of memory.
# View output only
./visualize.py output.raw
# Overlay input and output for comparison
./visualize.py input.raw output.raw
# The make target runs this on output.raw automatically
make visualize
Keyboard shortcuts inside the plot window:
| key | action |
|---|---|
| ← → | Scroll left / right along the timeline |
| Space | Reset zoom back to full view |
| drag | Rectangle-select a region to zoom into (RectangleSelector) |
| scroll wheel | Zoom in / out on the time axis |
The X axis can be toggled between time (seconds) and raw sample index using the radio buttons in the sidebar.
There are two unit tests — one for the fast sin/cos implementation and one for the LFO. Both compile separately and print pass/fail to stdout.
# Build and run both tests
make test
# Or individually
make test-sincos # tests/sincos.c — verifies fastsincos() accuracy
make test-lfo # tests/lfo.c — verifies LFO sinewave output
The LFO test generates a full-cycle sinewave and checks each sample against
the reference sinf() — it was written after a sign bug in
the cosine output was found and fixed (Fix the 'cos' part of fastsincos).
Any audio file that ffmpeg can read will work. Convert it to the raw format
first, then pass it to convert:
# Convert any file to s32le 48kHz mono
ffmpeg -i my_guitar_track.wav -f s32le -ar 48000 -ac 1 my_track.raw
# Run through an effect
./convert phaser my_track.raw | ffplay -f s32le -ar 48000 -ch_layout mono -i pipe:0
# The SeymourDuncan make target does this in a loop for a whole folder
# (edit the path in the Makefile to point at your own wav directory)
audio/myeffect.h. Define your state struct,
a myeffect_init(signed char pot[10]) function, and a
float myeffect_step(float in) function.
static struct effect myeffect_effect with
.name, .short_name, .init,
.step, and .pots filled in.
#include "myeffect.h" to audio/effect.h
(at the bottom with the other effect includes).
myeffect to the effects = list in the
Makefile, and optionally add a myeffect_defaults =
line for default pot values.
make myeffect to build and test it.
// Minimal effect skeleton
static struct {
float my_param;
} myeffect;
static float my_pot0(signed char pot) { return linear_pot(pot, 0.0f, 1.0f); }
static void myeffect_init(signed char pot[10])
{
myeffect.my_param = my_pot0(pot[0]);
}
static float myeffect_step(float in)
{
return in * myeffect.my_param; // replace with real DSP
}
static struct effect myeffect_effect = {
.name = "MyEffect",
.short_name = "MFX",
.init = myeffect_init,
.step = myeffect_step,
.pots = { { "Param", desc_none, my_pot0 } },
};