NOISFERATU / Texture Synth

June 24, 2026 · View on GitHub

Infinite textures and digital mayhem

noisferatu

You can check out a video here https://youtu.be/kAjsbi65Gq8
DIY kits are available at https://www.thonk.co.uk/shop/scaepe-noisferatu/
Project website https://scaepe.ing/noisferatu/noisferatu.html
My artist website https://www.robertheel.com/++/

Sections

Project Overview

NOISFERATU is a compact handheld generative texture synthesizer. 45 algorithms across 5 banks produce crackling textures, noisy scapes, evolving drones, harmonic blips and digital chaos. Shaped but never fully controlled by four knobs. They generate endlessly evolving textures, tones and rhythmic patterns; designed to surprise, resist repetition and never quite settle. Dial in a crackling drone, a stuttering melody, or pure digital chaos.

The front and back panel can be flipped.

It uses and abuses creative approaches like generative wavetables, BitBend address manipulation, probability gates, bitwise logic operations and wild combinations of these.

Noisferatu is a noise and texture synthesis engine for the Seeed Studio XIAO SAMD21 microcontroller.

Controlled by 5 potentiometers and 3 buttons. Emphasis on efficient integer math, cheap DSP operations, and creative generative textures.

Bank System:

  • Bank 1: Wavetables (9 algorithms) - Generative buffer-based textures
  • Bank 2: Noisy Textures (9 algorithms) - Noise-based processing
  • Bank 3: BitBend Wavetables (9 algorithms) - Buffer playback with binary address manipulation
  • Bank 4: Blips & Tones (9 algorithms) - Melodic/rhythmic generators
  • Bank 5: Logic Disorder (9 algorithms) - Bitwise chaos on oscillators

Total: 45 algorithms across 5 banks

Display

The Noisferatu uses a 4 digit 7 segment display with the TM1637 library by avishorp/TM1637 (LGPL-3.0).

By pressing the BANK button for 2 seconds you can toggle between display ON and display OFF.

Choosing ON the display will always display the current bank and algo.

If you choose OFF the display turns off after 500 milliseconds after you used one of the buttons.

Current Algorithm Bank Overview

Bank 1: Wavetables (9 algorithms)

Generative buffer-based textures using a shared 4000-sample wavetable. All variants regenerate on algorithm switch. Some auto regenerate during playback. GW17 and GW18 use advanced techniques (harmonic drones and quad bit manipulation) while still living in the wavetable bank.

#NameCore Techniquepot1pot2
1Sparse GlitchyRare noise chunks (10%), 1 triangle blip (220–7kHz), chunked silence injectionspeedsilence prob (max 20%)
2Dense MicroglitchTiny chunks (2/5/21 samples), denser noise (3%), high blip (2–2.2kHz)speedsilence prob (0–100%)
3Spacey Pulses33-sample chunks, 25% noise, no blip, chunked silence injectionspeedsilence prob (max 10%)
4Random Jump Glitch16/4/32 chunks, 50% noise, random buffer jump on silencespeedsilence prob
5Wandering WindowNoise/silence chunks + blip (600–2.5kHz), window randomly walks ±20 samplesspeedwalk rate
6Manual Window + SprayModerate density + blip (700–3.5kHz), fixed 0.5× speed, ±30 sample spray offsetwindow poswindow size
7Noise or Saw WindowChunks are 50/50 noise or saw (30–1kHz) + blip, wandering window playbackspeedwalk rate
8Chord Loop30% noise crackles + harmonic triangle pairs (root/2×/3×/4×), partial regen every 3s, pot2 stutter/freezeoctave multstutter prob
9BitBend QuadSuper-latched sparse noise, 3× big blips (600–900Hz), 4-layer address manipulation (XOR+HOLD+SET_0+SET_1)speedbit clock

Bank 2: Noisy Textures (9 algorithms)

Noise-based processing, filtering, and rhythmic textures. Slot 9 uses a PROGMEM sample (sample.h) — requires sample.h to be populated with raw 16-bit PCM data exported at 16kHz mono.

#NameCore Techniquepot1pot2
1Latched NoiseS&H noise with probability gateclock probclock rate
2DustSparse clicks + 1-pole lowpass filterlowpass cutoffdensity
3FMnoiseS&H noise modulates phase increment of sawtoothbase freqnoise S&H rate
4NoisegatesTwo triangle oscillators gate noise; osc2 has random walk on freqbase freqwalk step
5Saw ClicksTwo detuned saws, averagedsaw1 freqsaw2 freq
6Noise NOR Noise2 S&H noise sources~(held1 | held2) & 0x3FFpot1=S&H1 rate, pot2=S&H2 rate
7Dust BurstDust + Bernoulli-gated noise bursts (15% duty cycle)burst clock ratedust density
8Highpass NoiseBit-masked highpass noise + triangle LFO amplitude modulationAM depth (inverted)AM rate
9CracklesPROGMEM sample, random window jumps with chunked silence (57/113/331 samples). Speed 0.04–2× for pitch/texturespeed/pitchwindow size (200–4000 samples)

Bank 3: BitBend Wavetables (9 algorithms)

The BitBend technique: manipulate the read address before accessing the buffer is inspired by SoundScaper's "Clock Address Lines (SoundScaper is a great software for iOS by Igor Vasiliev). Instead of changing what is in the buffer, you change where you read from on each sample. Four modes: SET_0 (force bits to 0 → loops in lower addresses), SET_1 (force bits to 1 → jumps forward), XOR (flip bits → chaos), HOLD (freeze captured bits → locked loops). Algorithms combine 1–4 modes on different bit ranges simultaneously, with a clocked Bernoulli gate randomly evolving the bit positions. This means you can pretty much listen to a sound and it will keep evolving and changing, since the clocked Bernoulli gate probabilistically reassigns which bit positions are affected over time.

All 9: pot1 = playback speed, pot2 = bit clock rate

#NameBuffer SourceBit Operations
1BB ChaosDense noise+saw chunks1× generative, auto-picks mode randomly
2BB SparseSparse Glitchy (GW1)1× mode, pot2 selects mode directly (4 options)
3BB DualSpacey Pulses (GW3)XOR (low bits) + HOLD (high bits), 50/50 Bernoulli gate
4BB FreezeWandering Window (GW5)SET_0 (low bits) + HOLD (high bits), fixed 23Hz clock
5BB PingCustom sparse noise + high-freq tris (3–4kHz)XOR (low bits) + SET_1 (high bits), 50/50 Bernoulli gate
6BB MirrorCustom noise+saw with freq sweepSET_0 (low) + SET_1 (high) — conflicting forces lock address to middle range
7BB TripleCustomXOR (bits 0-2) + HOLD (mid) + SET_0 (bits 6-9), 50/50 Bernoulli gate
8BB SweepCustomDual HOLD at two speeds — slow clock on low bits, fast clock on high bits
9BB Triple BCustom sparse noise + 3× high-freq blips (4–6kHz)SET_0 (bits 0-1) + HOLD (bits 2-4) + SET_1 (bits 5-9), 50/50 Bernoulli gate

Bank 4: Blips & Tones (9 algorithms)

Melodic and rhythmic generators. Enveloped oscillators, scales, ring mod, Bernoulli gates.

#NameCore Techniquepot1pot2
1Random TriangleEnveloped triangle, 50% skip trigger, random freq 250–2.78kHztrigger rateenv decay
2Harmonic Tris3 triangles (1×, 3×, 4×) + 3 LFOs with irrational polyrhythm ratios (1, 4:3, √3)LFO1 rateLFO2 rate
3Fast Triangle~960 BPM clock, 75% skip, random bitcrush per trigger (3–12 bits)freqdecay
4Phrygian TriTriangle walks 8-note Phrygian scale via random walk, burst-modulated trigger rate, 50ms decayburst speedroot freq
5Ring ModTwo triangles multiplied (tri1×tri2)>>9(\text{tri1} \times \text{tri2})>>9, inharmonic sidebandsosc1 freqosc2 freq
6Noise OR SquareS&H noise bitwise OR'd with square wavenoise S&H ratesquare freq
7Major TrisFixed 220Hz major chord (root + maj3rd + perf5th), nested LFO AM on each voiceLFO1 rateLFO2 rate
8Bernoulli Minor 7th5Hz clock → 50/50 gate → two Bernoulli note gates (220/330 Hz vs 264/396 Hz = minor 7th chord)gate1 probgate2 prob
9Pentatonic BlipsRandom major pentatonic notes from 220Hz, 50/50 Bernoulli clock gateclock speedenv decay

Bank 5: Logic Disorder (9 algorithms)

Pure bitwise operations on live oscillator values. It's doing what a CMOS logic gate would do — but on 10 bits simultaneously, once per sample, 16000 times per second. Single ISR instruction creates complex harmonic content. In this bank, pot1 and pot2 each control an oscillator frequency (freq os1, freq os2). Exception: algo 1 has osc1 fixed, pot1 = osc2 freq, pot2 = osc3 freq.

#NameWaveformsOperationSound character
1Three Cascaded Squares3 squaresAM chain: sq1 modulates sq2, sq2 modulates sq3Rhythmic gating, stutters. Alarm and distorted mayhem.
2NOR Square2 squares~(sq1 | sq2) & 0x3FFNoisy drones.
3Tri OR SawTriangle + Sawtri | sawBuzzy and dense. High and low drones.
4Tri NOR Tri2 triangles~(tri1 | tri2) & 0x3FFFlickering tones. Chirping madness.
5Tri XOR Tri2 trianglestri1 ^ tri2Beating interference patterns. Noise and harmonics.
6Square XNOR2 squares~(sq1 ^ sq2) & 0x3FFNoisy beating.
7Square NAND2 squares~(sq1 & sq2) & 0x3FFTransmission and static interferences.
8Two SawsDual saws, averaged(saw1 + saw2) >> 1Rich sonic scapes.
9Square OR Square2 squaressq1 | sq2Marching toys.

Back to sections list

Hardware Setup

Microcontroller

  • Board: Seeed Studio XIAO SAMD21
  • CPU Clock: 48 MHz
  • Sample Rate: 16 kHz
  • DAC Resolution: 10-bit (0-1023)
  • ADC Resolution: 12-bit (0-4095)
  • Program Storage Used: ~50% (room for ~40+ more algorithms!)

Pin Mapping

AUDIO OUTPUT:
A0  (DAC_PIN)         → Audio output (10-bit DAC)

ALGORITHM PARAMETERS:
A1  (PARAM1_PIN)      → Pot 1 (algorithm-specific parameter)
A2  (PARAM2_PIN)      → Pot 2 (algorithm-specific parameter)

GLOBAL EFFECTS:
A3  (BITCRUSH_PIN)    → Global bitcrush (1-10 bits)
A4  (SAMPLE_RATE_PIN) → Sample rate reduction (1x-40x decimation)
A5  (VOLUME_PIN)      → Master volume (logarithmic curve)

BUTTON CONTROLS:
D9  (BUTTON_PREV)     → Previous algorithm - Btn3 AlgoPrev
D6  (BUTTON_ALGO)     → Next algorithm (within current bank) - Btn2 AlgoNext
D7  (BUTTON_BANK)     → Next bank (cycles through banks) Btn1 BankUp 

4 Digit DISPLAY:
D10  (TX)             → TM1637 DIO (7-segment display) DIO
A8  (SCK)             → TM1637 CLK (7-segment display) CLOCK

Audio Output

  • DAC Output: 10-bit centered at 511
  • Output Range: 0-1023 (maps to ~0-3.3V)
  • Algorithm Output: Signed 10-bit (-512 to +511)
  • Signal Chain: Algorithm → Sample Rate Reduction → Bitcrush → Dither → Volume → DAC
  • Final Output: analogWrite(DAC_PIN, DAC_CENTER + volumed)

Control Input

  • Potentiometers: Read as 12-bit (0-4095)
  • Normalized: Converted to 0.0-1.0 float (pot1_norm, pot2_norm)
  • Update Rate: Every loop iteration (~control rate)
  • Volume Curve: Quadratic (x²) for natural audio taper

Global Effects Chain

All algorithms pass through global effects before output:

  1. Sample Rate Reduction (pot A4): 1x to 40x decimation (16kHz down to 400Hz)
  2. Bitcrush (pot A3): 10-bit down to 1-bit (clean to extreme distortion)
  3. Dither: Symmetric ±1 dither to reduce quantization noise
  4. Master Volume (pot A5): Quadratic curve for smooth control

Back to sections list

File Structure

noisferatu.ino

Purpose: Main orchestrator - setup, loop, ISR, bank/algorithm routing

Key Components:

  • setup() - Initialize ADC/DAC resolution, pins, timer
  • loop() - Read pots, handle buttons, update parameters
  • TC5_Handler() - Audio ISR @ 16kHz, calls current algorithm
  • 4-bank system with algorithm enums
  • Button debouncing (50ms)
  • Wavetable regeneration on algorithm switch

hardware.h

Purpose: Physical I/O configuration and constants

Contains:

  • Pin definitions (DAC, ADC, buttons, display pins)
  • Sample rate and resolution constants
  • Global hardware state (pot1_norm, pot2_norm, masterVolume, globalBitcrush, sampleRateDecimation)
  • DAC center point (511)
  • Volume lookup table (quadratic, stored in PROGMEM)

Rule: Only hardware-level definitions. No algorithm-specific constants.

algos.h

Purpose: Pure DSP algorithm implementations

Contains:

  • Shared PRNG state and functions (noise1(), rand12())
  • All 45 algorithm implementations
  • Algorithm-specific state variables (phases, counters, envelopes, etc.)
  • Shared wavetable buffer (4000 samples) and all generation functions
  • GW17 Harmonic Drone Builder with partial buffer regen (partialRegenWaveform17())
  • GW18 BitBend Quad (4-layer address manipulation, lives in Bank 1)
  • BitBend variants (Bank 3): 9 algorithms using binary address manipulation on shared buffer
  • Pure math - no hardware knowledge

Algorithm Return: Signed 10-bit integer (-512 to +511)

Organization: Grouped by bank

params.h

Purpose: Parameter mapping - how pots control each algorithm

Contains:

  • Algorithm-specific constant ranges (MIN/MAX values)
  • updateXParams() functions for each algorithm (45 functions!)
  • updateAllParams() master update function (calls all param functions)

Rule: All algorithm parameters are updated every loop, regardless of which algorithm is active. This prevents parameter jumps when switching algorithms.

sample.h

Purpose: PROGMEM sample storage for the Crackles algorithm

Contains:

  • vinylCrackle[] — raw signed 16-bit PCM, 16kHz mono, stored in flash
  • VINYL_CRACKLE_BYTES — total byte count
  • VINYL_CRACKLE_SAMPLES — sample count (bytes ÷ 2)

To update: export from Audacity as raw signed 16-bit PCM at 16kHz mono, run xxd -i in terminal, paste byte values into array. Edit locally.

Back to sections list

Timer/ISR Configuration

TC5 Timer Setup

  • Timer: TC5 (16-bit mode)
  • Prescaler: DIV1 (48 MHz)
  • Frequency: 48,000,000 / SAMPLE_RATE = 3000 counts
  • Mode: Match Frequency (MFRQ)
  • Interrupt: MC0 (Match/Capture 0)

ISR Execution

void TC5_Handler()
{
  // Clear interrupt flag
  TC5->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0;
  
  // Get sample from current bank/algorithm
  int16_t sample = 0;
  switch(currentBank) {
    case BANK_WAVETABLES:
      sample = generativeWaveformX();
      break;
    // ... more banks
  }
  
  // Global effects chain
  // 1. Sample rate reduction
  // 2. Bitcrush
  // 3. Dither
  // 4. Volume
  
  analogWrite(DAC_PIN, DAC_CENTER + volumed);
}

Critical: ISR runs at 16 kHz. Keep it lean - no heavy processing, no delays.

Optimization Pattern (Core Principle)

// In params.h (called once per loop - float OK here):
algorithmPhaseInc = (uint32_t)(frequency * 268435.456f);  // Magic number for 16kHz
algorithmDecayCoeff = 1.0f - expf(-2.0f * PI * cutoff / SAMPLE_RATE);

// In ISR (called 16,000x/sec - integer only!):
phase += phaseInc;  // Auto-wraps at $2^{32}$
envelope *= decayCoeff;  // Simple multiply
output = (phase >> 22) - 512;  // Extract top 10 bits, center to ±512

Key Insight: Do expensive float math ONCE in updateParams(), convert to integer phase increment, then ISR just does cheap integer adds!

Back to sections list

Algorithm Development Workflow

Adding a New Algorithm

  1. In algos.h:
// Add state variables (volatile for ISR access)
volatile uint32_t newAlgoPhase = 0;
volatile uint32_t newAlgoPhaseInc = 0;

// Implement algorithm (inline for performance)
inline int16_t newAlgorithm()
{
  // Your DSP code here
  // Use 32-bit phase accumulators
  newAlgoPhase += newAlgoPhaseInc;
  
  // Extract and return signed 10-bit: -512 to +511
  return (newAlgoPhase >> 22) - 512;
}
  1. In params.h:
// Add parameter ranges
#define NEW_ALGO_FREQ_MIN 10.0f
#define NEW_ALGO_FREQ_MAX 1000.0f

// Add update function
void updateNewAlgoParams()
{
  // Map pot to frequency
  float freq = NEW_ALGO_FREQ_MIN + 
               (NEW_ALGO_FREQ_MAX - NEW_ALGO_FREQ_MIN) * pot1_norm;
  
  // Calculate phase increment (magic number for 16kHz)
  newAlgoPhaseInc = (uint32_t)(freq * 268435.456f);
}

// Add to updateAllParams()
void updateAllParams()
{
  // ... existing updates ...
  updateNewAlgoParams();  // <-- Add here
}
  1. In noisferatu.ino:
// Add to appropriate bank enum
enum BlipAndTonesAlgos {
  // ... existing algos ...
  BLIP_NEW_ALGORITHM,  // <-- Add here
  BLIP_ALGO_COUNT
};

// Update algo count
const uint8_t algoCountPerBank[BANK_COUNT] = {
  WT_ALGO_COUNT,
  NT_ALGO_COUNT,
  BLIP_ALGO_COUNT,  // Increment this
  LOGIC_ALGO_COUNT
};

// Add to switch statement in TC5_Handler()
case BANK_BLIPS:
  switch(currentAlgo) {
    // ... existing cases ...
    case BLIP_NEW_ALGORITHM:
      sample = newAlgorithm();
      break;
  }
  break;

Translating from Max/Gen or VCV Rack

Common Patterns:

Max/Gen/VCVC/Arduino (Efficient)
phasor~phase += phaseInc; out = phase >> 22;
noise~noise1() (10-bit signed)
>~if (signal > threshold)
*~a * b then scale: >> 10
+~a + b
sah~if (trigger) value = input;
slide~ / slewy += (x - y) * coeff; (1-pole filter)
triangle~See triangle generation pattern above
%~ (modulo)Phase wraps automatically!
Bernoulli gateif (rand12() & 1) for 50/50
Clock dividerif (++counter >= division) counter = 0;
Ring mod(sig1 * sig2) >> 9
VCA / AM(carrier * modulator) >> 10

Tips:

  • Max/Gen outputs -1 to +1, convert to -512 to +511
  • Phase accumulators wrap at 2322^{32} automatically - no modulo needed!
  • Use volatile for variables modified in ISR
  • Keep ISR code efficient - complex calculations go in loop()
  • Magic number: 268435.456f = $2^{32}$ / 16000 for Hz to phase increment

Back to sections list

Debugging Tips

Common Issues

  1. No sound: Check buttons/pots connections, verify correct bank/algorithm selected
  2. Distortion: Check algorithm returns -512 to +511 range, check for integer overflow
  3. Parameter not responding: Verify updateXParams() is called in updateAllParams()
  4. Buttons frozen/unresponsive: Expensive math in ISR (float division, powf(), expf())
  5. Clicks/pops: Check for division by zero, ensure phase wrapping is automatic
  6. Algorithm sounds wrong: Verify phase increment calculation uses correct magic number
  7. Volume issues: Check quadratic volume calculation, verify pot wiring (3.3V, not 5V!)

Serial Debugging (optional)

// In setup()
Serial.begin(115200);

// In loop() - NOT in ISR!
Serial.print("Bank: "); Serial.print(currentBank);
Serial.print(" Algo: "); Serial.println(currentAlgo);
Serial.print("Pot1: "); Serial.println(pot1_norm);

Warning: Serial prints are SLOW. Never use in ISR. Use sparingly even in loop.

CPU Load

If buttons become unresponsive, you've likely added expensive operations to the ISR.

Symptoms:

  • Button presses don't register
  • Pot changes lag
  • Audio glitches/dropouts

Solution:

  • Move expensive calculations to updateXParams() in loop
  • Use lookup tables for complex math
  • Replace float operations with integer math
  • Check for hidden float divisions

Performance Notes

  • ISR Overhead: Minimal when following efficient patterns
  • RAM Usage: Moderate - wavetable buffer (8KB), float state variables
  • Headroom: ~50% storage remaining for 30+ more algorithms

Back to sections list

Code Style Guidelines

  • Use volatile for ISR-shared variables
  • Use inline for algorithm functions (performance hint to compiler)
  • Keep parameter ranges as #define constants
  • Comment algorithm behavior, not syntax
  • Name state variables descriptively: bernTrisEnv1 not e1
  • Update all params every loop (prevents switching glitches)
  • Use const for truly constant values
  • Mark adjustable values with // ADJUST: comments
  • Group related algorithms in files with clear section headers

Magic Numbers Reference

// Phase increment for 16kHz sample rate
phaseInc = (uint32_t)(frequencyHz * 268435.456f);
// Where 268435.456 = $2^{32}$ / 16000

// Sample period from frequency
samplePeriod = (uint32_t)(SAMPLE_RATE / frequencyHz);
// For sample & hold timing

// Decay coefficient for envelope
decayCoeff = 1.0f - (3.0f / (decayTimeSeconds * SAMPLE_RATE));
// Approximate -60dB decay time

// 1-pole filter coefficient
alpha = 1.0f - expf(-2.0f * PI * cutoffHz / SAMPLE_RATE);
// Calculate in params, apply in ISR: state += alpha * (input - state)

// Extract top 10 bits from 32-bit phase
output = (phase >> 22);  // 0 to 1023
// Or centered: output = (phase >> 22) - 512;  // -512 to +511

// Bitcrush mask
mask = ~((1 << (10 - numBits)) - 1);
// E.g., 8 bits: ~((1 << 2) - 1) = ~3 = 0xFFFC

// Quadratic pot curve (for volume, slow LFOs)
curved = potValue * potValue;  // Or: pot_norm * pot_norm

// Cubic pot curve (even slower taper)
curved = potValue * potValue * potValue;

Back to sections list

Future Expansion Ideas

Potential New Algorithms

  • More Wavetable Variants: Different chunk patterns, multiple oscillator types
  • Karplus-Strong: Plucked string synthesis via (short) delay line + lowpass
  • Granular Synthesis: Tiny overlapping grains with envelopes — no true granular engine, but short crude buffer playback approximating grain density and texture without the scheduling overhead
  • More Bitwise Combinations: 16 possible 2-input logic operations
  • Probabilistic Sequencers: Euclidean rhythms, generative melodies
  • Feedback Loops: Previous sample as modulation source

Power consumption

Seeed Studio XIAO SAMD21, 25 mA
TM1637 Display, 12 mA

Total 37 mA


For AI Assistants

When adding new algorithms:

  1. Always read existing algorithms first to understand patterns
  2. Follow the three-file pattern: algos.h → params.h → main.ino
  3. Keep ISR lean - expensive math in params only
  4. All algorithms return signed 10-bit (-512 to +511)
  5. Parameters always update (all of them, every loop)
  6. Use 32-bit phase accumulators with automatic wrapping
  7. Calculate phase increments in params using magic number 268435.456f
  8. Test with buttons - if they freeze, you probably have expensive ISR code
  9. Comment adjustable constants with // ADJUST:
  10. Embrace cheap chaos - bitwise ops, Bernoulli gates, S&H

The synth thrives on generative unpredictability and efficient bit-twiddling.
Avoid complex math - embrace the limitations!

Licenses

Code → GPL v3
Hardware → CC BY-SA 4.0
Name and logo → All rights reserved

Robert Heel 2026

Back to sections list