You need three things installed before anything works:

toolused for
gccCompiling convert.c and tests. Any recent version.
ffmpeg / ffplayDecoding MP3 to raw PCM (ffmpeg) and playback (ffplay).
python3 + matplotlib + numpyOnly 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:

keyaction
← →Scroll left / right along the timeline
SpaceReset zoom back to full view
dragRectangle-select a region to zoom into (RectangleSelector)
scroll wheelZoom 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)
  1. Create audio/myeffect.h. Define your state struct, a myeffect_init(signed char pot[10]) function, and a float myeffect_step(float in) function.
  2. Define a static struct effect myeffect_effect with .name, .short_name, .init, .step, and .pots filled in.
  3. Add #include "myeffect.h" to audio/effect.h (at the bottom with the other effect includes).
  4. Add myeffect to the effects = list in the Makefile, and optionally add a myeffect_defaults = line for default pot values.
  5. Run 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 } },
};