Skip to content

therudywolf/bacillum

Repository files navigation

Bacillum

build

Virtual-analog polyphonic synthesizer plugin in the spirit of Access Virus TI, Nord Lead A1/4 and Roland JP-8000.

VST3 + Standalone for Windows/macOS, written in C++20 on top of JUCE 8.

root@bacillum:~$ ./synth -mode poly -voices 16 -engine VA

What works today

Synthesis engine

Block Status Notes
Voice manager done custom 16-voice pool, NOT juce::Synthesiser. Stealing priority: same-note > idle > released-lowest-env > oldest, with anti-click fast-kill of the stolen slot.
OSC1 / OSC2 done PolyBLEP-band-limited Sine / Triangle / Saw / Square+PWM / HyperSaw (7-voice JP-8000 supersaw) / Wavetable (mip-mapped, 8-frame morph). Pitch ±24 semi, detune ±50 cents, level.
OSC interop done Hard sync (OSC2→OSC1), ring mod (OSC1×OSC2), FM / phase-mod (OSC2→OSC1) on the classic-VA carrier path.
Sub osc done Independent sine / triangle / square, -1 or -2 octave switch, own level.
Noise done White (xorshift32, per-note seed) + pink (Paul Kellet).
Filter done Two models, selectable per patch: Cytomic TPT SVF (LP12, LP24 cascade, HP, BP, Notch, Peak) and Moog ladder (Huovilainen 2004, thermal non-linear, 2× oversampled — LP24/LP12). Drive (tanh) pre-filter.
Envelopes ×3 done ENV1 (filter), ENV2 (amp, soft-retrigger, velocity²), ENV3 (free, matrix-only). Linear ADSR.
LFO ×3 done LFO1 + LFO2 per-voice key-triggered, LFO3 global (free-running). 8 shapes (sine/tri/saw↑/saw↓/square/PWM/S&H/smooth-random); fade-in; free or tempo-synced (1/1…1/32, dotted/triplet). LFO1 has dedicated cutoff/pitch/amp routings; LFO2/3 route via the matrix. Mod-wheel adds vibrato on top (Virus/Nord-style).
Mod Matrix done 8 user slots, each source → destination × depth (±1). 14 sources (ENV1-3, LFO1-3, Velocity, Note, ModWheel, PitchBend, Aftertouch, Random/note, Constant), 15 destinations (cutoff, resonance, drive, pitch, OSC2 pitch, OSC1/2 PW, OSC1/2/sub/noise level, pan, amp, LFO1/2 rate). Pull-based, control-rate, sums on top of dedicated routings.
Glide / portamento done Per-voice fractional-note glide that chases the target at control rate; unison stacks slide together; seeded from the last played note.
Unison done 1–8 voices per note, symmetric cents detune, equal-power stereo spread per pair.
Mono / legato / poly done Note priority last; legato re-uses the voice without re-triggering envelopes.
Arpeggiator done Up / Down / Up-Down / Random / As-Played; tempo-locked rate (1/1…1/32), 1–4 octave range, gate length. Runs as a sample-accurate MIDI transformer before the engine.
Tempo sync done LFO1, delay and arp lock to host BPM via AudioPlayHead (fallback 120).
Pitch bend, sustain, AT, mod-wheel, panic done Sample-accurate MIDI dispatch in processBlock.

FX bus

Voice mix → 3-band EQ → Chorus/Flanger/Phaser → Stereo Delay → Reverb → Compressor → Limiter → Out.

FX Status Notes
EQ done 3-band (low shelf + peak + high shelf), RBJ biquads, TDF-II, allocation-free (recomputed per block on the audio thread).
Chorus / Flanger / Phaser done One unit with mode select. Quadrature LFO (90° L/R offset), Lagrange-3 delay line, 6-stage allpass cascade for phaser.
Delay done Stereo with independent L/R times (or tempo-synced), ping-pong cross-feedback, damping LP in feedback path.
Reverb done Dattorro plate (1997): input bandwidth LP → 4 input-diffusion allpasses → figure-8 tank (allpass + delay + damping, cross-coupled decay) → multi-tap stereo output. Size / damping / width / mix.
Compressor + Limiter done juce::dsp::Compressor (threshold/ratio/attack/release + makeup) followed by a brick-wall juce::dsp::Limiter at -0.3 dBFS for output safety.

Presets

Notes
Factory bank 34 code-defined patches across Init / Bass / Lead / Pad / Keys / Pluck / Brass / Strings / Arp / Sequence / FX, several showcasing wavetable scan, dual-filter split/serial, FM, hard sync, ring mod, the mod matrix and the Dattorro plate. Sparse override model (each patch only stores what differs from Init), applied on top of a full reset-to-defaults.
Browser Header bar with < [Category / Name ▼] > — prev/next + dropdown. Loading drives the APVTS so every knob animates to the new value.
State Full plugin state saved/restored as APVTS XML (getStateInformation).

Visualisation (real-time)

Lock-free SPSC ring buffer fed by audio thread post-FX; UI Timer at 30 Hz reads without blocking the audio path.

Component Notes
Oscilloscope Rising zero-cross trigger, 1024-sample window, cyan trace with glow.
Spectrum analyser 2048-pt Hann-windowed FFT, log frequency axis, log magnitude (dB), per-bin attack/release smoothing, peak-hold dots, cyan→red gradient fill.

Input

  • MIDI 1.0 from any DAW / external hardware.
  • PC keyboard piano via juce::MidiKeyboardComponent — works in Standalone even when a knob has focus (keys forwarded by editor override). Layout: A W S E D F T G Y H U J K (one octave white+black), Z / X octave down/up.
  • On-screen clickable keyboard.

UI

  • Custom wolf-cyberpunk LookAndFeel: black / cyan / blood-red palette, Consolas mono everywhere, sharp angular sections with cyan accent stripes, terminal-style header (root@bacillum:~$ ... with blinking caret), concentric cyber knobs with bipolar fill-from-centre.
  • Preset browser bar, ARP panel, dual-filter + saturator, EQ + compressor, 8-slot mod-matrix grid, oscilloscope + spectrum analyser.
  • Resizable (1040×1080 → 2400×2000).
  • ~121 parameters in APVTS, host-automatable, state save/load via XML.

Build

Prerequisites

  • Windows: Visual Studio 2022 Build Tools + Windows 11 SDK + CMake ≥ 3.22.
  • macOS: Xcode 14+ (untested in this revision but the CMake project is portable).
  • JUCE 8.0.4 is fetched automatically via FetchContent — no submodule, no Projucer.

Build commands

# From a Developer PowerShell for VS 2022 (so cl.exe is on PATH)
cd C:\path\to\bacillum
cmake -B build -S . -G "Visual Studio 17 2022" -A x64
cmake --build build --config RelWithDebInfo --parallel 8

Artefacts land in:

build\Bacillum_artefacts\RelWithDebInfo\
  Standalone\Bacillum.exe                                 ~14 MB
  VST3\Bacillum.vst3\Contents\x86_64-win\Bacillum.vst3    ~15 MB

Drop the .vst3 into C:\Program Files\Common Files\VST3\ to expose it to FL Studio, Ableton Live, Cubase, Reaper, Studio One, Bitwig…


Architecture

            ┌────────────────────────────────────────────────┐
   MIDI ──► │  PluginProcessor::processBlock                 │
            │  ├─ sample-accurate sub-block split            │
            │  ├─ MidiKeyboardState.processNextMidiBuffer    │ ← PC + on-screen kbd
            │  └─ for each sub-block:                        │
            │      VoiceManager::setParams(snapshot)         │
            │      VoiceManager::render(L,R,start,n)         │
            │      ▼                                         │
            │  16 × Voice:                                   │
            │    OSC1 ─┐                                     │
            │    OSC2 ─┼─► mix ──► drive ──► SVF ──► VCA     │
            │    Sub  ─┤                ▲          (AmpEnv)  │
            │    Noise┘                 │                    │
            │                       FilterEnv + LFO1 + Vel   │
            │      ▼                                         │
            │   per-voice DC-block + equal-power pan         │
            │      ▼                                         │
            │  Σ Voices  ──► Chorus/Flanger/Phaser ──►       │
            │              Delay ──► Reverb ──► MasterGain   │
            │      ▼                                         │
            │  Push post-FX to AudioVizBuffer (lock-free)    │
            └────────────────────────────────────────────────┘
                            │
                            ▼
                 Editor (Message thread)
                  ├─ APVTS attachments
                  ├─ Oscilloscope (30 fps timer)
                  └─ Spectrum analyser (30 fps timer + 2048-pt FFT)

Hard rules followed in the audio thread:

  1. No allocations (no new, no STL containers that grow, no std::string).
  2. No locks. GUI↔audio is std::atomic (APVTS raw pointers) + SPSC ring buffers.
  3. No exceptions (everything reachable from processBlock is noexcept).
  4. No virtual dispatch in hot loops.
  5. DC blocker on every voice output.
  6. Sample-accurate MIDI via sub-block splitting on samplePosition.
  7. Smoothed parameters where it matters (juce::SmoothedValue for master gain).
  8. Control-rate update for cutoff (every 16 samples) to avoid burning tan().

Scientific references

The DSP is built on canonical work from the audio-research community. Code-level citations are inline in the relevant headers.

Oscillators / anti-aliasing

Filters

Effects

Interpolation

Real-time audio engineering

Reference codebases (read for sanity)


Roadmap

See ROADMAP.md.


License

The source code in source/ is © therudywolf, all rights reserved (for now). JUCE is dual-licensed (GPLv3 / commercial); building Bacillum statically pulls in JUCE, so any redistribution must comply with JUCE's GPLv3 unless you hold a commercial JUCE licence.

About

Wolf-cyberpunk virtual-analog polyphonic synthesizer plugin (VST3 + Standalone). C++20 / JUCE 8. HyperSaw, TPT SVF, LFO, unison, FX chain, oscilloscope + spectrum analyser.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors