Nebulae-Granular_Looper_Pedal, image 1 from its README
1/2
Firmware

Nebulae-Granular_Looper_Pedal

Hype13/100

A port of the Qu-Bit Nebulae v2 granular looper to a guitar pedal, running on an Electrosmith Daisy Seed3 in a PedalPCB Terrarium platform. Phase vocoder and granular processor running in parallel off

Nebulae-Granular_Looper_Pedal is custom firmware for the PedalPCB Terrarium, by @De-Pattern. Latest release v1.32 (September 2026), 4 stars on GitHub.

forPedalPCB Terrarium·by@De-Pattern

Downloads
0
Stars
4
Forks
0
Commits
33
Last commit
Download v1.32

nebulae_v1.32-BUILD1.bin· 113 KB

Custom license· De-Pattern/Nebulae-Granular_Looper_Pedal·downloads come from the creator’s GitHub releases

33
Commits
4
Releases
33 commits and 4 releases in the last year
Activity · last 12 months

Nebulae - Granular Looper Pedal

A port of the Qu-Bit Nebulae v2 granular looper to a guitar pedal, running on an Electrosmith Daisy Seed3 in a PedalPCB Terrarium platform

Phase vocoder and granular processor running in parallel off a shared 120-second live buffer, with a blend between them

Nebulae faceplate

Credits

Original engineStephen Hensley / Qu-Bit Electronix, a_granularlooper.instr (Csound, 2017), QB_Nebulae_V2
Blend reworkalex-thibodeau , a 2-way engine blend with independent dry/wet
Hardware platformPedalPCB Terrarium
DSP moduleElectrosmith Daisy Seed3

This is a transcription from the original Csound source, not a reimplementation from descriptions. Every sound-producing constant (the tanh/cubic density curve, the 0.20 RMS ceiling, the seven grain windows, the constant-power blend laws, mincer's FFT size and phase-locking) was taken from the instrument file and validated numerically against a Python reference before any firmware was written.

License: MIT. The upstream Qu-Bit firmware is MIT (Copyright © 2019 Qu-Bit Electronix, Inc.), so this port carries that forward alongside its own copyright. See LICENSE. MIT permits use, modification, distribution and commercial use; the only obligation is to keep the copyright notice and permission text with the source. libDaisy is likewise MIT.


Architecture

                       live input
                           |
                    +------+------+
                    |  DC block   |
                    +------+------+
                           |
          +----------------+----------------+
          |           record buffer         |   120 s mono float32, A/B double-buffered
          |        (volatile, SDRAM)        |   46.1 MB of 64 MB
          +----------------+----------------+
                           |
              +------------+------------+
              |        syncphasor       |<---- LFO 1  read position
              |                         |<---- LFO 2  loop length
              +--+-------------------+--+
                 |                   |
        +--------+------+   +--------+---------+
        | mincer        |   | partikkel        |
        | phase vocoder |   | granular         |
        | FFT 2048      |   | max 10 grains    |
        | 4x overlap    |   | spray, pitch rnd |
        | phase-locked  |   | RMS limiter 0.20 |
        +--------+------+   +--------+---------+
                 +---------+---------+
                     BLEND  constant power
                           |
          record tap ------+
                           |
                     freq shifter   page 3
                           |
                       bandpass     page 3  <---- LFO 2 (optional destination)
                           |
                     output gain ---- soft limiter ---- out

The record tap is taken before output gain and before the page-3 effects, so neither monitoring level nor the shifter and filter ever reach the buffer. Effects are monitoring and playback only, applied once, non-destructive.


Hardware

Required

ItemNotes
PedalPCB Terrarium (PCB351) Rev 2
Daisy Seed364 MB SDRAM, USB-C. Seed 1 also works
125B enclosure
6 x B10K 16 mm potsPOT2 and POT5 must be CENTER DETENT (Speed and Pitch)
4 x SPDT ON/ON toggle, 6 mmstandard solder-lug pedal toggle, same footprint as every other PedalPCB board
2 x SPST momentary footswitchnot 3PDT latching
2 x LED + resistors
2 x 20-pin female header

Passives per the Terrarium build doc. Note C2/C3/C6 are specified MLCC, not film. At 1 µF the footprint is 2.5 mm lead pitch, which does not exist in film.

Pin map (Terrarium Rev 2)

PedalPCB labels the header pins GPIO n; libDaisy calls the same pin D(n−1).

ControlPedalPCBlibDaisyControlPedalPCBlibDaisy
POT1GPIO17D16 / A1SW1GPIO11D10
POT2GPIO18D17 / A2SW2GPIO10D9
POT3GPIO19D18 / A3SW3GPIO9D8
POT4GPIO20D19 / A4SW4GPIO8D7
POT5GPIO21D20 / A5LED1GPIO23D22
POT6GPIO22D21 / A6LED2GPIO24D23
Audio inpin 16AUDIO_IN_LFS1GPIO26D25
Audio outpin 18AUDIO_OUT_LFS2GPIO27D26

Controls

Three pages of knob assignments, selected by SW1 and FS1.

PageHow to reach itLabelled on the panel
1, primarySW1 downyes
2, shiftSW1 upyes
3, tertiarySW1 up and FS1 heldno

Page 3 lasts only as long as you hold FS1. Release and you are back on page 2.

Full matrix

Nebulae build 1 control map
Pot1 Primary2 SHIFT3 TERTIARY
POT1START, loop startLFO 1 RATEBANDPASS freq
POT2SPEED (detent)BLEND LFO DEPTHLFO 2 RATE
POT3SIZE, loop lengthSPRAYLFO 2 DEPTH
POT4DENSITYOVERLAPSHIFT MIX
POT5PITCH (detent)OUTPUTFREQ SHIFT
POT6BLENDLFO 1 DEPTHGRAIN PITCH RAND
Switch1 Primary / 2 SHIFT3 TERTIARY
SW1SHIFT, selects page 1 or 2
SW2SRC, MIX or INflip = toggle LFO 2 to bandpass
SW3WINDOW, expodec or bartlett(free)
SW4FREEZE, held or run(free)
FS1tap = bypass, hold 1 s = reset pages 2 and 3hold = page 3
FS2tap = record, hold 1 s = clear buffer

Page-3 defaults: bandpass centre (off), LFO 2 rate 0.02 Hz, LFO 2 depth 0, shift mix full wet, freq shift centre (off), pitch rand 0.


Page 1, primary

STARTLoop start position within the recording
SPEED-4x to +4x. Centre detent is unity. Negative is reverse
SIZELoop length, squared curve, scaling the space between Start and the end
DENSITYGrain trigger rate, 0.12 to 983 Hz, hybrid tanh and cubic curve
PITCH-3 to +2 octaves. Centre detent is unity
BLENDConstant power across vocoder, dry and granular

POT2 and POT5 are centre detent, and the detent lands exactly on unity for both. The module uses encoders here; the knob curve is remapped so a pot behaves the same way.


Page 2, shift

LFO 1 RATE0.02 to 2 Hz, exponential. Default 0.02 Hz, one cycle every 50 seconds
BLEND LFO DEPTHLFO 1 to Blend, unipolar, squared curve. Default 0
SPRAYGrain position randomisation, squared curve
OVERLAPGrain length as a multiple of the grain period
OUTPUTMonitoring level only, never reaches the recorder
LFO 1 DEPTH0 to a full loop, squared curve. Default 0

SPRAY is the module's Start secondary, restored. At zero every grain spawns from the playhead, so concurrent grains all read identical audio and differ only in envelope phase. The result is successive stutters rather than a cloud. Turning it up spawns grains from random positions across the loop. This is the control that makes it sound granular.

BLEND LFO DEPTH routes LFO 1 to Blend as a second destination. It shares LFO 1's rate on SHIFT+POT1 but keeps its own depth, independent of the read-position depth on POT6.

Unipolar, matching the read-position path: the Blend knob is the floor of the sweep and the LFO only pushes upward from it, so you always know where the bottom is.

depth 0.50 -> Blend at 0.50 sweeps 0.50 to 0.75
depth 1.00 -> Blend at 0.50 sweeps 0.50 to 1.00

One consequence: it only sweeps toward granular. With Blend at centre the motion runs dry to granular, never toward the vocoder. To sweep into vocoder territory, set Blend low and let the LFO rise through dry.

Input is recorded at unity. There is no input level control.

OUTPUT never reaches the recorder, so boosting it does not make overdubs compound on themselves.


Page 3, tertiary

Hold FS1 while SW1 is up. There are no panel labels for these.

BANDPASSCentre is off. CCW sweeps down to 120 Hz, CW up to 6 kHz
LFO 2 RATE0.02 to 2 Hz, exponential
LFO 2 DEPTH0 to plus or minus one octave of loop length, squared curve
SHIFT MIXBlends shifted against dry. Default full wet
FREQ SHIFTCentre is off, then 0.5 Hz to 100 Hz either side
GRAIN PITCH RANDPer-grain detune, 0 to plus or minus 120 cents

The page-3 toggle latch

Hold page 3 and flip SW2 either direction. LED2 blinks to confirm, and LFO 2 now modulates the bandpass as well as loop length. Flip again to turn it off.

The firmware reads the flip, not the position. Entering page 3 takes a reference and never acts on it, so the page cannot adopt wherever the lever happens to be sitting. Which way you flip is irrelevant; only the transition counts.

SW2 keeps doing its normal SRC job throughout, including during page 3, so the lever never lies about its own state. That means a page-3 flip also changes SRC, which is the deliberate trade: an extra flip is better than a switch whose position means nothing.

SW3 and SW4 have no page-3 assignment, so the same latch pattern could be extended to them at one flip each and no knobs. Standing preference is not to: both already carry a primary function, and stacking more onto a lever is the kind of overloading this design keeps trying to remove. See the Build 2 section, Tier 2 A4.


Catch and pickup

No secondary ever jumps to wherever the shared knob happens to sit. Nothing adopts on a page change, on the first visit, or at power-on.

On page 1 this is pure catch: the parameter waits indefinitely until the knob crosses its stored value. The panel legend means something there, so knob position stays honest.

On pages 2 and 3 catch has a give-up threshold. If you turn a knob more than about 15% of its travel without crossing the stored value, it stops waiting and takes over where you are, crossfading over 25 ms so there is no click. That fixes the case where a stored value sits at an extreme: without it, a parameter defaulting to 0.0 needs a full counter-clockwise sweep before it responds at all, and reads as dead.

Merely changing pages never causes a takeover. The travel counter resets on every page change, so a jump only ever follows a deliberate turn.


Footswitches and LEDs

FS1tap (on release) = bypass, hold 1 s = reset pages 2 and 3
FS2tap = record, hold 1 s = clear buffer
LED1lit = engaged
LED2lit = recording, blinks = buffer cleared, blinks = page-3 latch changed

FS1 in detail

FS1 does three different jobs depending on SW1 and how long you hold it.

SW1ActionResult
downtapbypass toggles, on release
downhold 1 sresets every page 2 and page 3 parameter to its default
upholdpage 3, for as long as it is held
uptapnothing

With SW1 up, FS1 will not bypass. While SW1 is up, FS1 is the page-3 modifier, so it neither toggles bypass nor starts the reset timer. Whatever bypass state you were in is held, so if the pedal was engaged, LED1 stays lit and you cannot switch it off. Flip SW1 down first, then tap FS1. The reset is likewise only reachable with SW1 down.

Bypass fires on release, not press, so that holding FS1 for a reset does not also flip bypass on the way in. That costs a few hundred milliseconds, imperceptible by hand.

The reset

Holding FS1 for one second with SW1 down returns all nine page-2 and page-3 parameters to their power-on defaults: LFO 1 rate and depth, dub level, spray, overlap, output, LFO 2 rate and depth, bandpass, shift mix, freq shift and pitch rand.

Page 1 is not touched, so Pitch and Speed are not reset. Their centre detents are the reset for those two: the detent lands on exactly 1.0000 in both cases.

Page 1 is deliberately untouched. Resetting it would leave all six knobs uncaught and the pedal would ignore every control until each was swept. After a reset the affected pots are uncaught, which is the point: the hidden pages go back to a known state.

It is kept separate from the FS2 buffer clear.


Effects (page 3)

Three processors, applied after the record tap. They are monitoring and playback only: the buffer never contains them, so overdubbing with an effect engaged neither bakes it in nor compounds it across passes. Chain order is freq shifter, then bandpass.

Freq shift

Bode-style single-sideband shifter: Hilbert transform through two 4-section allpass chains (Niemitalo coefficients), then quadrature modulation against a quadrature oscillator, one sideband only. That detail is what makes it inharmonic rather than ring mod. Image rejection measured at 46 dB.

Dead zone at centre, then exponential 0.5 Hz to 100 Hz each side, so most of the travel sits in the 1 to 30 Hz zone where sidebands colour the tone without destroying pitch.

1:00  ->   1.3 Hz     slow beating
2:00  ->   3.8 Hz
3:00  ->  11.2 Hz     sidebands start reading as timbre
4:00  ->  33.5 Hz
5:00  -> 100.0 Hz

An earlier version ran 2 Hz to 2 kHz. Everything past about 3:30 read as a resonant sweep rather than a shift, so two thirds of the knob were unusable.

SHIFT MIX (POT4) blends shifted against dry. Hearing both together is the classic Bode usage: the two beat against each other, which reads as movement rather than the flat detune of a fully wet shift. Around halfway is where the beating is strongest.

Bandpass

TPT state-variable filter, 12 dB/oct, Q of 1.0, last in the chain. Centre is off. The wet amount fades in over the first 15% of travel either side, so there is no jump leaving the dead zone, and the frequency mapping is exponential so the knob tracks pitch.

0.00 ->  120 Hz
0.25 ->  339 Hz
0.50 ->  OFF
0.75 -> 2257 Hz
1.00 -> 6000 Hz

A bandpass throws away everything outside its passband, so perceived level drops even though the peak itself is unity: measured -14 dB at 200 Hz against -0.7 dB at 6 kHz before compensation. Makeup gain scales with wet amount, reaching about +5 dB fully engaged.

24 dB/oct was considered and rejected. A steeper filter removes more energy, so it gets quieter, not louder. Makeup gain is the right lever.

With the page-3 latch on, LFO 2 sweeps it bipolar and exponential, plus or minus 2 octaves at full depth, centred on whatever the knob is set to. Clamped to 40 Hz and 12 kHz, so extreme settings narrow the sweep rather than wrapping.

knob sets     lfo -1     lfo +1     span
   339 Hz        85       1356      4.0 octaves
  2257 Hz       564       9026      4.0 octaves
  6000 Hz      1500      12000      3.0 octaves

Grain envelope, expodec

The decay is two exponentials summed, a fast component plus a slow one. A single exponential cannot be both snappy and long-tailed, because how fast it falls and how far it falls are the same number, so lengthening the tail always softens the initial drop. Two stages behave like a plucked string instead: the transient bites, then a low tail hangs on.

                to -6dB   to -12dB   to -20dB    mean
single exp        15.8      25.2       37.7     0.1460
two-stage         11.9      22.3       50.6     0.1463

Percentages of grain length; the attack is a fixed 6.25% in both. Snappier at the top, longer at the bottom, and the mean is unchanged so it needs no level compensation. Both endpoints are exactly zero and the decay never rises, so grain boundaries stay silent.

Everything scales with grain length, so the change reads as proportional opening rather than a fixed release time and is most obvious at long grains.

Grain pitch randomisation

The module's Pitch secondary, restored. Each grain gets its own detune, assigned once at launch and held for that grain's life. Per grain, not per sample: per sample would be FM.

Stock is rand(amt * 1200), bipolar cents, so plus or minus an octave at full. That is far too wide to stay musical, so this uses a squared curve to plus or minus 120 cents.

knob   max cents   rms cents
0.25      7.5         4.3
0.50     30.0        17.3
0.75     67.5        39.0
1.00    120.0        69.1

Quarter travel is chorus territory. Half is audible detune. Most of the knob stays subtle. RMS runs about 58% of the maximum because the distribution is uniform, so typical grains sit well inside the extreme.

Pair it with SPRAY. Spray decorrelates where grains read, pitch rand decorrelates how fast. Together they are what makes the grain cloud sound like a cloud.


LFO 1, read position

An addition, not part of the original module. It modulates read position only.

Shape

A sine with its phase warped before the sine is taken:

warp(p) = p / (2s)                    for p < s
          0.5 + (p - s) / (2(1 - s))  for p >= s
out     = sin(2 * pi * warp(p))

s is the skew, fixed at 0.65. At s = 0.5 this reduces to an ordinary sine.

The warp stretches the first 65% of the cycle across the first half of the sine and compresses the remaining 35% into the second half, so the rise is slow and the fall is quick. It stays smooth throughout, with no corners or discontinuities.

One cycle sampled in 24 steps, against a pure sine:

skew 0.65  +0.20 +0.39 +0.57 +0.72 +0.85 +0.94 +0.99 +1.00 +0.97 +0.90 +0.80 +0.66
           +0.50 +0.32 +0.12 -0.15 -0.50 -0.78 -0.96 -1.00 -0.90 -0.68 -0.37  0.00

sine       +0.26 +0.50 +0.71 +0.87 +0.97 +1.00 +0.97 +0.87 +0.71 +0.50 +0.26 -0.00
           -0.26 -0.50 -0.71 -0.87 -0.97 -1.00 -0.97 -0.87 -0.71 -0.50 -0.26  0.00

The rise takes 15 of 24 steps; the fall takes 9.

lfo visual

How it moves the sound

It offsets read position, not loop geometry. Loop start and loop length are computed before the LFO is applied and are never touched by it, so playback rate and loop boundaries stay fixed. Only the point being read within the loop moves.

It is unipolar. The sine's -1 to +1 range is mapped to 0 to +1, so the offset only ever pushes forward from the playhead, never behind it. The sweep therefore anchors at the Start position.

uni  = (sin_out + 1) * 0.5          ->  0 to 1
read = phasor + uni * depth^2

Depth is squared. At full depth the offset covers an entire loop, so the read position scans forward through the whole buffer and returns.

Both engines follow it together, since they share the read position. The vocoder time-warps as the read point moves and grains spawn from the moving position. At low depth this is tape-like wobble; at high depth it becomes a slow scan through the loop.

Rate is 0.02 to 2 Hz, exponential (50 seconds per cycle at the slow end).

The asymmetry is what reads as motion rather than oscillation. A symmetric sine feels like rocking in place; this drifts forward and resets.

Controls

SHIFT + POT1LFO 1 RATE
SHIFT + POT6LFO 1 DEPTH (0 = off, output is bit-identical to no LFO)

Blend is a second destination, on SHIFT+POT2. It shares this rate knob but has its own depth, so read position and Blend move together in time at whatever amounts you set. Both are unipolar, anchored at their knob positions. In build 2 both LFOs reach every destination through bipolar attenuverters instead.


LFO 2, plain triangle

Page 3. Rate on POT2, depth on POT3. Normalled to loop length, and routable to the bandpass as a second destination by flipping SW2 while page 3 is held.

Deliberately the dull one. Where LFO 1 is a phase-warped sine with character, this is the symmetric triangle a sampler gives you: bipolar, no skew parameter, no output curve. When you want obvious motion rather than shape, this is the one to reach for.

-0.75 -0.50 -0.25 +0.00 +0.25 +0.50 +0.75 +1.00 +0.75 +0.50 +0.25 +0.00 -0.25 -0.50 -0.75 -1.00

Computed per sample rather than per block, so its corners land exactly. At block rate a corner can be up to one block late, which at 2 Hz is 2% of the period and harmless, but the per-sample form costs almost nothing and removes the question.

Loop length, the normalled destination

Bipolar, multiplying loop length by 2^(depth^2 * lfo), so full depth breathes between half and double length while the playhead rate follows inversely. Multiplicative, so it feels uniform across Size's squared curve. Recording uses the unmodulated length, so the buffer itself never breathes, only playback.

Earlier versions gave LFO 2 a skew that tracked its own rate. That came out in v1.33: with LFO 1 already providing a shaped waveform, a second shaped one was redundant, and a plain triangle is more useful as a contrast.


How to use it

Load your first loop

  1. SW2 to IN
  2. Tap FS2, recording starts
  3. Play
  4. Tap FS2, recording stops

In IN mode nothing else affects what is recorded. Loop length is set by where you stop.

Hear it

  1. BLEND sets the mix. Fully CCW is the phase vocoder, centre is dry, fully CW is granular
  2. SPRAY (SHIFT+POT3) is what turns stuttering into a grain cloud
  3. DENSITY and OVERLAP shape the texture. Under FREEZE, grain length is the stutter length, so Overlap makes it longer and Density retriggers it faster

Overdub

  1. SW2 to MIX
  2. Tap FS2 once

Recording punches in at the current playhead, runs exactly one loop pass, and stops itself. You do not tap FS2 again. Loop length is locked, so an overdub cannot shorten the loop.

The record source is engine output plus your input, independent of BLEND, and recorded at unity. Page-3 effects are not captured, so overdubbing with the shifter up will not bake it in or compound it across passes.

Reach page 3

With SW1 up, hold FS1. Release to return to page 2. Every page-3 control needs catching, but the give-up threshold means about a sixth of a turn is enough.

Clear and reset

Hold FS2 one second to clear the buffer. Hold FS1 one second, with SW1 down, to reset pages 2 and 3 to defaults. LED2 blinks either way.

Considerations

  • The buffer is volatile. Power down loses it
  • Layers are mixed destructively into one buffer. There is no undo and no way to adjust a layer after it is recorded
  • With SIZE below maximum an overdub covers only that fraction of the loop and stops early. That is stock behaviour, since the module ties overdub length to loop size the same way
  • At unity speed and unity pitch with SPRAY and PITCH RAND at zero, every concurrent grain reads identical audio and differs only in envelope phase. That is why it sounds stuttery rather than granular. Raise either one, or move PITCH off its detent
  • A page-3 flip of SW2 also changes SRC, by design. The lever never lies about its own physical toggle position

git repo diagram

Versions

Current: nebulae_v1.33-BUILD1.bin

The two builds are now separate lines. Build 1 is the 1.x firmware on this page. Build 2 is 2.x, built with make EXTRA_DEFS=-DBUILD2=1, and is documented under Build 2 plans. A release ships only the binary for the build it is for: no BUILD2 binary appears in a 1.x release and no BUILD1 binary in a 2.x one.

VersionChange
1.33Current. LFO 2 becomes a plain symmetric triangle, bipolar, no skew, computed per sample. SHIFT+POT2 becomes BLEND LFO DEPTH: LFO 1 to Blend, unipolar, sharing LFO 1's rate but with its own depth. DUB LVL removed, input records at unity
1.32Built on v1.27. Two-stage grain decay: a fast component plus a slow one, so the transient bites harder than before while the tail hangs on longer. Record tap moved ahead of the page-3 effects, so they are non-destructive. DUB LVL kept on SHIFT+POT2
1.31Superseded. Same decay change but built on v1.27 while labelled as v1.29, so it lacked the record-tap move. Replaced by 1.32, which is the same idea on a verified base
1.30Superseded. LFO 2 rate narrowed to 0.05 to 1.5 Hz, but built on a tree mistaken for v1.29. The range is still wanted; see Build 2 plans O3
1.29Record tap moved ahead of the page-3 effects. SHIFT+POT2 became BLEND LFO DEPTH and DUB LVL was removed, both reverted in 1.32
1.28Expodec decay floor 0.001 to 0.003, lengthening the tail by shallowing the bend. Superseded by 1.32's two-stage decay, which lengthens the tail without softening the attack
1.27LED2 blinks to confirm the FS1 parameter reset on both builds
1.26Page-3 LFO latch moved to SW2, which keeps its normal SRC job throughout
1.25LFO 2 routable to the bandpass by flipping a toggle while page 3 is held, LED2 confirms
1.24Grain pitch randomisation on page 3. LFO 2 skew now follows its own rate. Phaser removed
1.23Catch gains a 15% give-up takeover on pages 2 and 3. Page 1 stays pure catch
1.22Phaser deepened to 3 chains and 10 stages. Bandpass makeup gain
1.21Page 3 rearranged. Phaser speed tracked the LFO 2 rate
1.20Sweepable bandpass, last in the chain
1.18 to 1.19Barber-pole phaser, later removed
1.17Size LFO skew exposed, later folded into rate
1.16Size LFO shape: mirrored skew plus an output curve
1.15Freq shift dry/wet mix
1.14Freq shift range cut to 0.5 Hz to 100 Hz. The old 2 kHz top read as a resonant sweep
1.13Reset moved to FS1 hold. Bypass moved to release
1.12Parameter reset for pages 2 and 3
1.11Build 2 variant gated behind a single BUILD2 define
1.10Third page on SW1 up plus FS1 held. Size LFO and freq shifter. Catch generalised to three pages
1.9.7OUTPUT no longer reaches the recorder. With output boosted, every overdub had been writing the loop back louder and compounding
1.9.6DUB LVL boots at unity. The init still meant unity on the old input-level curve but -12 dB on the new one
1.9.5Record source is engine output plus input, independent of BLEND. At either Blend extreme the dry factor is zero, so overdubs had captured engine output only
1.9.4SPRAY on SHIFT+POT3, ported from the Csound. Window moved onto the SW3 toggle
1.9.3Record fixed. The overdub latch check ran before the engine had acted on a fresh tap, so it cleared the latch on the block that set it
1.9.1Punch-in overdub. Also fixed a v1.9 bug where booting with SHIFT up made the shift page adopt knob positions outright
1.9Catch resumes without a re-catch only if the parameter was live when leaving the page
1.8RMS limiter moved to control rate. Fixed the grain crackle at high Overlap
1.7Skip the engine whose blend coefficient is exactly zero
1.6Output level on SHIFT+POT5
1.5Unipolar LFO depth. Catch tolerance widened from 0.2% to 2%
1.1 to 1.4Catch adopt, expodec floor, LFO curve, density taper. All reverted
1.0Audio block size 64 to 512. The critical fix, see below
0.1 to 0.9Bring-up: clear, LFO, heartbeat, input level, pot smoothing, FTZ, DC block, soft limit

Two branches. The 1.x line is the maintained one. 2.x-ALEXTBLEND keeps the 2-way engine blend with dry on its own knob, but has no SPRAY, no page 3 and no effects.

Controls for the 2.x-ALEXTBLEND branch (click to expand)

The 2.x branch replaces the stock 3-way blend with a 2-way engine crossfade and moves dry onto its own control. Under the stock law the vocoder and grains never overlap, since voc fades out by centre and grain fades in after it. The 2.x law lets both sound together.

PrimarySHIFT
POT1STARTLFO RATE
POT2SPEED (detent)DRY/WET
POT3SIZEWINDOW
POT4DENSITYOVERLAP
POT5PITCH (detent)OUTPUT
POT6BLEND, vocoder to granular onlyLFO DEPTH

BLEND becomes purely the balance between the two engines. Centre gives 0.707 of each rather than pure dry. DRY/WET on SHIFT+POT2 is the main output mix and boots fully wet.

This branch has no SPRAY, no page 3 and no effects section, and its Window is still on a knob. It is far behind the 1.x line and is kept only for anyone who prefers that blend behaviour.


Deviations from stock

MonoTerrarium is single in / single out. Loses grain stereo spread
120 s buffervs 5 min. Volatile, with no file, USB, SD or sample loading anywhere
Secondaries mostly droppedMost _alt params fixed at 0, which is stock-at-defaults. SPRAY and grain pitch rand are restored
Speed/Pitch remapCentre detent lands on unity. Endpoints preserved exactly. The module uses encoders, we use pots
SPRAY, OUTPUTSPRAY is the module's Start secondary, restored. OUTPUT is an addition, since the Terrarium is unity gain throughout
Effects are non-destructiveThe record tap sits ahead of them, so the buffer never contains freq shift or bandpass and overdubs cannot compound the effect
Overdub ignores BLENDStock scales the recorded dry by the blend dry factor, which is zero at both extremes. Correct for the module, wrong for a looper
Catch modeOne pot serving two parameters requires it
Expodec windowReplaces the stock linear ramp-down, on a toggle so it can be A/B'd
LFO 1Not stock. Modulates read position only, so loop geometry is untouched
LFO 2, freq shift, phaser, bandpassNot stock. Page 3 additions. Freq shift uses the Hilbert core from kuttor's SuperNova firmware with a re-mapped range. The barber-pole phaser is an original implementation of a well-documented algorithm class
Input/output levelTerrarium is unity gain; a guitar sits ~20 dB below where the grain RMS limiter engages
DC block, output soft limit, block size 512Platform necessities

Freeze and Reset are not on footswitches: Freeze is literally kspeed = 0 in the original, so a toggle does it, and Reset only mattered for CV sync.


Notes from the port

Block size was the one serious bug. mincer computes an entire frame (two forward FFTs, one inverse, two 1025-bin loops, and a 2048-sample interpolated SDRAM read) inside a single audio callback. At 64 samples that's 1.33 ms of budget against ~1.4 ms of work, so one block in eight overran: a dropout 94x/second that sounded like digital garble. Nebulae itself runs Csound on a Pi with a ~42 ms output buffer, so its frame work is invisible.

Sample-exact diffing is the wrong test for a phase vocoder. div = 1/(hypot(prev)+1e-20) reaches ~1e20 at empty bins, so float rounding compounds through the feedback path. Frame 0 matches the reference to 7.4e-08; after that the waveforms diverge while remaining spectrally identical to −58 dB. Acceptance criteria are frame-0 agreement, unity gain, exact pitch ratios and magnitude spectrum. The granular side, being non-recursive, is held to sample-exact and passes at 2e-07.

giHamming is not Hamming. The instrument declares ftgen 20, 9, 1 and comments it "Hamming Window". GEN20 type 9 is Sinc. This matters: sinc reaches exactly 0 at both endpoints, a real Hamming bottoms at 0.08 and would click at every grain boundary.

The grain RMS stage is a limiter, not a leveler. Identity below 0.20, attenuation above. It never boosts, which is why input level matters on a guitar-level platform.

Density never reaches its stated 2500 Hz. The hybrid tanh/cubic scalar tops out at 0.906, so the real maximum is ~983 Hz.


Building from source

git clone --recursive https://github.com/electro-smith/libDaisy.git
cd libDaisy && make -j
cd .. && mkdir nebulae && cd nebulae
# copy in: main.cpp terrarium.h nebulae_dsp.{h,cpp} nebulae_engine.{h,cpp} Makefile
make                          # -> build/nebulae.bin  (build 1)
make EXTRA_DEFS=-DBUILD2=1    # -> build 2, button and bi-colour LED
TARGET = nebulae
CPP_SOURCES = main.cpp nebulae_dsp.cpp nebulae_engine.cpp
LIBDAISY_DIR = ../libDaisy
SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core
include $(SYSTEM_FILES_DIR)/Makefile

On Windows, Electrosmith's Daisy Toolchain installer bundles arm-none-eabi-gcc, make and dfu-util.


License

MIT. See LICENSE.

  • Original engine © 2019 Qu-Bit Electronix, Inc. (MIT)
  • Blend rework derived from alex-thibodeau's fork (inherits MIT)
  • This port © 2026 Kenny Rakentine (MIT)
  • libDaisy © Electrosmith (MIT)

If you fork this, keep all four attributions.


Build 2 plans

Waiting on a Terrarium PCB restock. Everything else is ordered or on hand.

How to read this section

Build 2 has been designed more than once. Rather than deleting older plans, they are kept as tiers. Nothing below is dead unless it is in Tier 4.

TierMeaning
1, PRIMARYThe current plan. Build this unless there is a reason not to
2, ALTERNATESuperseded but coherent and buildable. Each entry says what replaced it and why
3, OPENUndecided or unverified. Resolve before assembly
4, REJECTEDRuled out, with the reason, so it is not re-proposed

When a Tier 1 entry changes, the old version moves to Tier 2 rather than being removed.


TIER 1, PRIMARY

Design settled. This is the layout to build.

Nebulae build 2 control map

Panel

SW1 stays a toggle. A seventh hole is added for an illuminated momentary button with a bi-colour LED, which carries the LFO layer. LED2 at FS2 becomes a red/green bi-colour.

Knobs

Pot1 Primary2 Shift3 TertiaryLFO page, both
1STARTTAPE DECAYBANDPASS freqdepth to read position
2 ●SPEEDDUB LVLBANDPASS QLFO RATE
3SIZESPRAYWINDOWdepth to loop length
4DENSITYOVERLAPSHIFT MIXdepth to bandpass
5 ●PITCHOUTPUTFREQ SHIFTLFO SKEW
6BLENDWINDOW DECAYGRAIN PITCH RANDdepth to blend

Every page-2 and page-3 control keeps the pot it has today. Only the four slots freed by the LFO rework are newly filled: tape decay, bandpass Q, window, window decay.

Switches, footswitches, LEDs

SW1SHIFT, page 1 or 2
SW2SRC mix or in
SW3WINDOW slot 2: expodec or rampdown
SW4FREEZE. Page-3 flip toggles LFO 2 divisions, LED2 confirms
FS1tap = bypass. Hold = page 3, regardless of SW1
FS2tap = record, hold 1 s = clear buffer
FS1 + FS2hold 1 s = reset pages 2 and 3
Buttontap cycles LFO 1 (red), LFO 2 (green), off. Hold = momentary, last page used
LED1blue or violet, engaged
LED2red recording, green playback, amber SOS active, blinks for confirmations

The reset moves to both footswitches

FS1 + FS2 held together resets pages 2 and 3. That frees FS1's hold to mean page 3 unconditionally, with SW1's position irrelevant.

This is what lets page 3 stay on FS1 without the build 1 collision, where holding FS1 means page 3 with SW1 up and a parameter reset with SW1 down and nothing on the panel says which, so reaching for page 3 with SW1 down wipes both hidden pages instead. Two footswitches at once is unambiguous and impossible to hit by accident.

Worth backporting to build 1 at the next firmware update there.

The LFO layer

Two pages, one per LFO, identical layouts. Which LFO you are editing is the LED colour, not the arrangement. One page per LFO is what makes two fit: each needs a rate and a skew, four controls against two spare knobs.

Pots 2 and 5 carry rate and skew because Speed and Pitch take no LFO, so those knobs are otherwise idle in this layer. Pot 2 already reads Speed, close enough to its LFO-page job to cost nothing. Pot 5 is centre detent, which suits skew: detent at symmetric, CCW one way, CW the other.

Depths are bipolar attenuverters, centre off. Inverting a skewed LFO turns slow-rise-fast-fall into its mirror, which is different motion rather than a phase flip, and on read position it sweeps backward from Start instead of forward. Centre-off also means depth defaults to the middle of its travel rather than an extreme, which sidesteps the dead-knob trap in O2 entirely.

Four destination slots per LFO, mirroring page 1 minus Speed and Pitch, except that bandpass takes Density's slot. Density does not need an LFO; bandpass sweep is already proven on build 1 and would otherwise be a regression. See O5.

Both LFOs reach every destination, including the same one at once. No hard split.

What the four freed slots hold

WINDOW, page 3 POT3. The stock continuous sweep across all seven grain windows, restored. WinMix already crossfades the whole bank; SW3 merely picks one of two for slot 2. This is the largest sound-shaping control not currently exposed, and it returns to the pot it occupied before v1.9.4 moved it to a toggle. SW3 keeps slot 2's expodec versus rampdown choice.

WINDOW DECAY, page 2 POT6. Sweeps the expodec grain envelope from the original single exponential toward a two-stage plucked-string curve.

knob       -6dB      -12dB     -20dB     mean
0.00       15.8      25.2      37.7     0.1460    exactly the v1.27 curve
0.50       14.1      22.7      38.1     0.1414
1.00       11.9      22.3      51.8     0.1510

Knob at zero is bit-identical to the original, not an approximation: the fast component becomes the whole curve with its time constant set to 1/ln(1000), which is exactly 0.001^t. Turning up introduces the slow component, so the transient sharpens while the tail stretches. Mean level is flat across the sweep, so it needs no compensation, and both endpoints stay exactly zero.

It lengthens and sharpens together, which is inherent: a single exponential cannot get a longer tail without softening, so lengthening requires a second stage and that stage is what steepens the front.

BANDPASS Q, page 3 POT2. Fully CCW is exactly 1.0, the shipped value, so the default is the current sound and turning up only adds. Ceiling 4.0.

Q      cycles   at 120 Hz   at 900 Hz   at 6 kHz
1.0      2.2      18 ms       2 ms      0.4 ms    shipped, knob CCW
2.0      4.4      37 ms       5 ms      0.7 ms
4.0      8.8      73 ms      10 ms      1.5 ms    knob CW
6.0     13.2     110 ms      15 ms      2.2 ms    rejected

This topology never self-oscillates: damping is 1/Q and only reaches zero at infinite Q, so it rings progressively longer instead. Ring time in cycles is constant, so the low end of the sweep is the worst case and the same Q rings 50x longer at 120 Hz than at 6 kHz. Q=4 at 120 Hz is 73 ms, audibly resonant but not pitched. Q=6 is 110 ms, which is a drone.

TAPE DECAY, page 2 POT1. Undecided. Sound-on-sound: write existing * decay + new so old layers fade instead of accumulating, which is how a tape loop with a partly-erasing head behaves. At 0.9 a layer is 20 dB down after 22 passes, at 0.98 it lingers for a hundred.

This is the only genuinely new DSP in build 2; everything else exposes or re-routes code that already exists. The risk is feedback: the engine output sits inside the loop, so both the grain RMS limiter and the output soft limiter end up in the feedback path. That is a tuning job, not a one-line change. The slot is reserved; whether SOS ships is still open.

Firmware status

Preliminary firmware exists: nebulae_v2.0-BUILD2-PRE.bin. BUILD2 now means the seventh-hole button and bi-colour LED2; the old button-replaces-SW1 arrangement (Tier 2 A1) has been removed from the source, so SW1 is a toggle in both builds.

Built and verified in software: five catch pages, both LFO pages, all eight depth attenuverters, the window and window-decay knobs, bandpass Q, LFO 2 divisions with loop retrigger, the button state machine and the FS1 + FS2 reset.

Not done: tape decay reads its knob into a variable and does nothing, pending the SOS decision. Untested on hardware: LED colours, button feel, the 350 ms tap threshold.

Pins as built, all flywired from the header underside with 1K per LED leg:

D0  LFO button          D1  button LED red
D2  button LED green    D3  LED2 green   (LED2 red stays on the existing LED2 pin)

LFO 2 on build 2 keeps its skew

Build 1's LFO 2 is a plain triangle with no skew at all. Build 2's keeps a skew knob on pot 5, so it sweeps continuously from reverse saw through triangle to ramp-up saw: skew toward 0 is the reverse saw, quick rise and long fall. Rate runs 0.1 to 15 Hz, which reaches a fast trill, and the per-sample computation is what keeps the corners clean up there.

In division mode the rate instead becomes one of seven whole-number ratios of the loop, from a quarter-cycle per loop to sixteen, and the phase resets when the playhead wraps. Locking the period to the loop is what makes that reset seamless: an unsynced LFO reset mid-cycle steps its output, which clicks on loop length or filter frequency. Fast divisions with a reverse saw are the trill.

Button mounting

The DAIER illuminated tact switch has no nut and no panel bushing, so it cannot be fastened the way a pot or jack is. Mount it on a small perfboard held against the underside of the enclosure top, cap protruding through the hole.

  • Position between the two LEDs, slightly lower if it fits. Check clearance against the Terrarium PCB below before committing
  • Drill the hole slightly larger than the cap so it does not bind at the end of travel
  • Set standoff height so the cap sits proud at rest by more than the switch travel, or it bottoms out on the enclosure before actuating
  • Hot glue works but cures rigid and is awkward to remove. Nylon standoffs with double-sided foam, or a shim stack, are easier to revise
  • Wire and test the switch before gluing anything

Free Seed pins

D0 to D6, D11 to D14, D24, D27 to D30.

Solder to the underside of the Terrarium female header, never to the Seed itself, so the module stays removable for bench flashing. Do all flywiring before the board goes in the enclosure. Each LED leg needs its own 1K.

Ordered and on hand

Black powder-coated 125B, black toggles, 3 mm red/green bi-colour LEDs, illuminated SPST momentary buttons, Daisy Seed3, centre-detent and plain B10K pots, Lumberg jacks, headers, OPA2134, trimmers, 100 uF polymer rail caps, WIMA film set, Panasonic 10 uF, Davies knobs.


TIER 2, ALTERNATE

A1. Button replaces SW1 in the SW1 hole

Tap latches shift, hold gives page 3. No seventh hole.

This is what the current BUILD2 define actually builds, so it is the only Tier 2 entry that exists in firmware today.

Superseded by: the seventh hole, because it left FS1 carrying a page-3 hold whose meaning depended on SW1 with nothing on the panel to say which.

Still viable if: drilling a seventh hole proves impractical, or the button cannot be mounted cleanly. Costs no new firmware.

A2. Button is a page-3 modifier only

SW1 stays a toggle, seventh hole added, button is a plain momentary page-3 modifier with its LED lit while held, and FS1's page-3 special case is deleted.

Superseded by: LFO mode, which uses the button for a layer that needs it more.

The problem it solved is now solved elsewhere. On build 1, holding FS1 means page 3 with SW1 up and a parameter reset with SW1 down, and nothing on the panel says which, so reaching for page 3 with SW1 down wipes both hidden pages. Tier 1 moves the reset to FS1 + FS2 instead, which fixes the collision without spending the button on it.

Still viable if: the LFO layer turns out not to be worth a dedicated control, in which case this is the cheapest thing to do with the seventh hole.

A3. Hard split of knobs between the two LFOs

Each knob permanently assigned to one LFO. The button LED indicates which is selected.

Superseded by: two independent LFOs, judged less restrictive and less confusing.

Its one advantage: depth knobs leave the mode entirely, since each knob has exactly one depth value regardless of selection, so you never need to know which page you are on to set a depth. Worth revisiting if two pages of depth values prove hard to track in practice.

A4. LFO destination latches on SW3 and SW4

Additional LFO destinations bought one toggle flip at a time, no knobs required. The pattern already exists on SW2.

Superseded by: continuous per-destination depth in LFO mode, which is strictly more expressive than a fixed-depth on/off latch.

Standing preference against: both toggles already carry a primary function, and stacking more onto a lever is the overloading this design keeps trying to remove.

A5. LFO mode absorbs page 3

The button carried the LFO layer and page 3's contents moved onto pages 1 and 2, so page 3 disappeared entirely and FS1 went back to unconditional bypass and reset.

Superseded by: keeping page 3. The arithmetic never quite worked, since four page-3 survivors had to fit three freed page-2 slots, and the only way to close the gap was to fix SHIFT MIX at a constant. Independent shift mix is one of the more musical controls on the pedal and its adjustability is the point.

What it got right: FS1 returning to one unconditional meaning. Tier 1 now achieves that by moving the reset to FS1 + FS2 instead.


TIER 3, OPEN

O1. RESOLVED. Page 3 is kept

The question was whether page 3 would empty once the LFO destinations moved into the LFO layer, which would have let FS1 return to one unconditional meaning.

Answered by counting. Only four page-3 survivors would have had to fit three freed page-2 slots, and closing that gap meant fixing SHIFT MIX at a constant. Not acceptable: independent shift mix is one of the more musical controls on the pedal.

So page 3 stays, only the LFO rate and depth controls leave it, and the FS1 collision is solved separately by moving the reset to FS1 + FS2. See Tier 1.

O2. RESOLVED. Centre-off depths sidestep the catch trap

The worry was that every LFO depth defaults to zero, an extreme, which is the known shape of the dead-knob trap: a parameter sitting at 0.0 needs a full counter-clockwise sweep before pure catch picks it up. With two LFO pages that would have been every depth knob, twice.

Bipolar attenuverter depths solve it by construction. Off is the centre of travel, not an end of it, so a knob left anywhere lands within reach. The 15% give-up takeover still applies and should extend to both LFO pages, but it is no longer load-bearing.

O3. LFO rate range rework

Confirmed on build 1: LFO 2 past 12:00 CCW is not dead, just extremely slow. The cause is the rate range, not catch and not the detent.

Proposed: raise the slow floor from 0.02 Hz to 0.1 Hz, a 10 second cycle. Keep the 2 Hz ceiling. That narrows the span from 6.6 octaves to 4.3, so every degree of rotation covers less frequency ratio and the whole sweep gains resolution, including the fast end that currently feels narrow.

0.02 to 2 Hz     50.0 s to 0.50 s   6.6 octaves   today
0.05 to 1.5 Hz   20.0 s to 0.67 s   4.9 octaves   built as v1.30, wrong base, not shipped
0.06 to 1.2 Hz   16.7 s to 0.83 s   4.3 octaves   finer still
0.10 to 2 Hz     10.0 s to 0.50 s   4.3 octaves   loses the slow end

0.05 to 1.5 Hz is the one to rebuild. It keeps a genuinely slow floor while cutting the share of travel slower than a 10 s cycle from 35% to 15%, and every tenth of the knob then covers 0.49 octaves instead of 0.66, so the whole sweep is 26% finer. Narrowing both ends keeps the law a single exponential, so equal rotation still covers equal frequency ratio.

Raising the ceiling does the opposite. It widens the span and makes every degree coarser. Only the floor move gets what was asked for.

The 46.9 Hz aliasing limit is in Tier 4 as the hard ceiling. It is nowhere near anything worth setting here.

Measure before changing. Read the actual mapping in the source rather than assuming.

O4. Faceplate artwork

The repo image is Nebulae1.9.7Decal_9-8-26.png. The filename is a version behind.

Previously the artwork was still correct because page 3 was unlabelled. An LFO layer changes that. If pots 2 and 5 carry rate and skew in LFO mode, the panel arguably needs secondary labelling the current decal does not have. Decide before etching.

O5. RESOLVED. Bandpass takes pot 4, Density does not get an LFO

The LFO layer has exactly four depth slots per LFO: pots 1, 3, 4 and 6, since pots 2 and 5 carry rate and skew. Mirroring page 1 would have made those Start, Size, Density and Blend, leaving bandpass with no slot. Build 1 can already route LFO 2 to the bandpass at plus or minus 2 octaves, so build 2 would have shipped with less modulation than build 1: a proven bandpass sweep traded for Density modulation that has never existed on this pedal.

Pot 4 carries bandpass depth. Density does not need an LFO. Size keeps its slot, since it is LFO 2's normalled destination today and loop length breathing is one of the better modulations on the pedal.

The only cost is that pot 4's LFO-page label reads bandpass while its primary label reads Density, the one place the LFO layer stops mirroring the printed panel. Page 3 is kept (see O1), so bandpass frequency stays on page 3 POT1 and no parameter is ever separated from its own depth knob.

O6. Input boost

Undecided. Non-inverting OPA2134 stage between the input jack and the Terrarium IN pad, gain 4 to 6x, 100K trimmer, powered from the board +5 V and VREF, shielded cable routed away from the Seed.

This is the only gain in the chain that can improve signal to noise, since OUTPUT is applied after the converter and lifts signal and noise identically. A guitar currently hits the ADC around 25 dB below full scale, wasting that much converter range.

It does not worsen mixer noise. The mixer is downstream and sees a hotter, cleaner signal, so it needs less makeup gain of its own.

Test it for free first by putting a clean boost pedal in front of the current build and listening on headphones. That is electrically the same experiment.

Gain above roughly 6x clips IC1.1 before the converter sees it, since the audio rail is +5 V with VREF at 2.5 V, giving about 3.5 Vpp of swing.

O7. Terrarium PCB restock

Alert is set. No alternate source exists. PedalPCB has not published Terrarium board files, so there is nothing to send to a fab.

O8. Panel-mount USB-C pigtail

Optional, so the Seed can be reflashed without pulling it. Round hole, avoids cutting a rectangular slot in cast aluminium.


TIER 4, REJECTED

DIP switches

Funbox uses them to select which of several firmwares to load. This project has one engine, and the page latch already covers configuration from the panel without taking the back off.

Audio rate LFO

Block size 512 puts the control rate at 93.75 Hz, so anything above about 46.9 Hz aliases. Reaching audio rate would mean restructuring the block size, which is fixed at 512 because a mincer frame does not fit in 64. Structurally closed, not a tuning question.

Shape selection buttons

Bastl Thyme style, eight LFO shapes on eight dedicated buttons. There is no panel space, and swept skew on a triangle already covers ramp-down through triangle through ramp-up continuously, which is most of what those shapes provide. The one thing skew cannot give is random.

Adding functions to toggles

Both spare toggles already carry a primary function. Standing preference: do not add functions to toggles unless there is no alternative.


Known limitations

  • Noise floor. The Terrarium's analog path is unity gain, so a guitar hits the ADC 20 to 30 dB below full scale. OUTPUT is applied after the converter and cannot improve SNR. The only real fix is analog gain ahead of the pedal. See the input boost under Build 2 plans
  • No stereo. Seed pins 17/19 are unused; a second output buffer would give dual mono. True stereo needs a second vocoder and grain cloud. CPU is fine, memory is the limit
  • Bypass is buffered, not true bypass. Audio always passes through the codec. Loss of power means loss of signal
  • ~43 ms vocoder latency against the dry path. Inherent to a 2048-point FFT, and stock behaviour
  • The module locks Speed/Start/Size during recording and snaps Speed/Pitch to unity after an overdub. Neither is implemented, and the second is impossible with pots