Noaidi is a virtual analog synthesizer whose sound engine runs on a $25 FPGA board. 256 oscillator-filter partials, computed every sample at 96 kHz. No DSP chip and no operating system in the audio path.
Source: github.com/Thorwegian/noaidi-fpga-synth
Recorded straight from the board, unedited.
The demo runs the same patch values as the instrument, so you hear its character rather than a recording of it. Turn the knobs; they map to the same MIDI controls.
An ESP32-C3 reads MIDI, allocates voices and maps every knob to engine parameters. It writes those parameters over SPI to a Tang Nano 20K, where a single time-multiplexed datapath computes all 256 partials once per sample. Every musical decision is made in firmware; the FPGA only computes samples.
The mod envelope can move cutoff by ±16 octaves and the cutoff control by ±8, so any destination can be driven end to end.
A small pipelined processor on the FPGA runs the envelopes, LFOs and modulation routings as one straight-line program, about 1,470 cycles per pass at 48 kHz, and writes the results into the partials' parameter memory.
Voice
| Polyphony | 32 voices, 256 partials |
|---|---|
| Structures | 1+1, 7+1, 4+4 partials per voice |
| Oscillators | 2 per voice: saw, pulse, triangle, sine |
| Tuning | Coarse ±12 st, fine ±0.5 st, mix, pulse width 50 → 5 % |
| Unison | Detune per step, stereo spread to hard pan |
| Pitch resolution | log₂ UQ4.10, about 1.17 cents |
Filter
| Topology | 2 × TPT / ZDF state-variable, per partial |
|---|---|
| Modes | Lowpass, bandpass, highpass · 12 or 24 dB/oct |
| Cutoff | ±8 octaves, 14-bit with CC 74 + 106 |
| Resonance | Butterworth to Q ≈ 32, self-oscillation reachable |
| Key tracking | 0 → 200 %, follows bend and LFO pitch |
Modulation
| Envelopes | Amp and mod ADSR per voice, in gateware, velocity amounts |
|---|---|
| LFOs | 2 · 0.03 → 30 Hz · saw, pulse, tri, sine → PW, resonance, pitch, cutoff |
| Mod envelope | Bipolar, square-law, ±16 octaves to cutoff |
| Processor | CSP: pipelined control processor, 2048-row program memory, one pass per 48 kHz period |
| Performance | Mod wheel, bend (RPN range 1–12 st), sustain, omni and per-channel multitimbral |
Audio
| Engine rate | 96 kHz · 768 clocks per sample |
|---|---|
| Transport | Q4.14 audio, Q8.28 filter states |
| Outputs | S/PDIF 48 kHz (primary), I2S 96 kHz |
| Limiter | 26-bit mix accumulator, sat24 |
Hardware
| Engine | Sipeed Tang Nano 20K, Gowin GW2AR-18C, 20,736 LUT4 |
|---|---|
| Clock | 73.728 MHz from the on-board MS5351 |
| Control | ESP32-C3 Super Mini, 4 MB flash, ESP-IDF v6 |
| Link | SPI mode 0, 40 MHz, 16-bit address / 32-bit data |
| MIDI | DIN in via optocoupler, second panel port |
| Storage | FAT partition, TOML patches, banks.toml |
Project
| Gateware | SystemVerilog, Yosys + nextpnr, Verilator 5 testbenches |
|---|---|
| Firmware | C on ESP-IDF, host-side unit tests |
| License | CERN-OHL-S v2, strongly reciprocal |
| Author | Thor H. Linløkken, Norway |
Each patch is one TOML file on a FAT partition in the ESP32's flash. Open it in any editor or keep it in git. The firmware clamps out-of-range values, ignores keys it does not know, and never formats your partition on its own.
banks.toml lists which patch answers each Bank Select and Program Change, numbered 0 to 16383.make fw-storage-read pulls the whole library to a folder on your computer. make fw-storage-write pushes it back.# Patches/Factory/Pads/Supersaw.toml format = 1 [voice] structure = "7+1" osc_mix = 0 unison_detune = 0.07 # semitones per step unison_stereo = 100 [[osc]] wave = "saw" tune = 0 [[osc]] wave = "sine" tune = -12 # sub, one octave down [filter] type = "lowpass" slope = 24 cutoff = 370 # Hz at C4 resonance = 1 key_track = 100 [amp_env] attack = 0.042 decay = 3 sustain = -3.76 # dB release = 11 velocity = 50 [mod_env] destination = "cutoff" depth = 24 # semitones sustain = 93.75 [[lfo]] shape = "triangle" rate = 1 destination = "pitch" depth = 0.094 [part] bend_range = 2
No vendor licence or proprietary bitstream tools. The gateware builds with Yosys and nextpnr, simulates in Verilator, and loads with openFPGALoader. The firmware is plain ESP-IDF.
| Part | Role | Street price |
|---|---|---|
| Sipeed Tang Nano 20K | Sound engine, GW2AR-18C FPGA | ≈ $25–35 |
| ESP32-C3 Super Mini | MIDI, voices, patches, console | ≈ $3–8 |
| 6N138 optocoupler + DIN socket | MIDI input | ≈ $2 |
| TOSLINK transmitter or I2S DAC | Audio out, optical or analog | ≈ $3–10 |
Prices are typical listings in late 2026 and vary by seller and shipping. The reference wiring needs no custom board.
Toolchain
Build steps are in the repository README.
Cost. A $25 FPGA board gives 32 voices, each with eight oscillators and two filters, at 96 kHz. Commercial polysynths with a quarter of that cost several hundred to a few thousand dollars.
On the GW2AR-18C: 44 % of the LUTs, 77 % of the flip-flops and 43 of the 46 block RAMs. Block RAM is the tight one.
Parameter changes reach the engine on a 1 kHz tick, so at most 1 ms after the MIDI byte is parsed. The audio pipeline is 26 clock stages at 73.728 MHz, well under one sample.
The engine runs at 96 kHz and decimates by two for the S/PDIF output, which is the primary path. The I2S output carries the full 96 kHz stream for a DAC.
No, and they need not be. The engine runs at 96 kHz, so the aliasing of a plain saw or pulse lands above the hearing range, and the decimation to 48 kHz removes it before the output. That is what the 96 kHz is for.
Yes. It takes 5-pin DIN MIDI and uses standard controller numbers, so any keyboard or sequencer with a MIDI out drives it with no software in between.
Not yet. Today you build it from a Tang Nano 20K, an ESP32-C3 and a few parts, following the repository. A release build is the next milestone.
So a patch is readable without the instrument: real units, comments allowed, diffs that make sense in git.
Watch the repository for releases, read the design documents, or clone it and build one now.