MIDI Synthesiser

Year
2025–26

Role
Solo

Built with
Java, JavaFX, Rust, WebAssembly

Team
Solo

Links
Java repo
Rust port
Demo video

JUNE'S LOGUE

JAVA SYNTHESISER

MIDI INPUT

OSCILLATOR

WAVEFORM

LFO

LFO WAVEFORM
LFO FREQUENCY1.0 Hz

FILTER

CUTOFF1000 Hz
RESONANCE3.0
MOD RANGE2000 Hz

AMP ENVELOPE

ATTACK0.005 s
DECAY0.100 s
SUSTAIN0.40
RELEASE0.400 s

FILTER ENVELOPE

ATTACK0.010 s
DECAY0.300 s
SUSTAIN0.50
RELEASE0.100 s

GLOBAL CONTROLS

MASTER VOLUME100%
PAN DEPTH0.40
PRE-FILTER GAIN-3.0 dB
POST-FILTER GAIN0.0 dB

Arpeggiator

Added for this site; not part of the original synthesiser.
Rate
Tempo
drag, scroll or arrow keys; double-click resets
Turn the arp on, then hold a key.

Control with this arpeggiator, or bring your own MIDI keyboard

Octave 4

Plug in a keyboard and choose it from MIDI INPUT in the panel (Chrome and Edge). With the arp on, it picks the chord root; with it off, it plays the synth directly.

I wrote the engine by hand in Java. The version you’re playing is a Rust port, made with agents so it can run in the browser. I made it the day Fable 5 came out, to test the model, and it did the port in one shot. Watch the Java demo
Play the keys with your mouse or your computer keyboard. Pan depth and the LFO decide where each note sits in the stereo field.
Recorded from the Java version Arpeggiated Chord Stabs Mono Bass

I built this to find out how far I could push the JVM for real-time audio, and to learn digital signal processing properly by writing it myself.

What I built

June’s Logue is a polyphonic synthesiser in Java with a JavaFX interface. I wrote the engine by hand, with no third-party DSP libraries. Each voice has an oscillator (sine, saw, triangle or square), a resonant low-pass filter, and two ADSR envelopes, one for volume and one for the filter cutoff. A global LFO handles stereo panning, and voice stealing makes sure a new note always gets a voice. It reads MIDI note, velocity and control change messages, so I played it from Logic Pro.

Here’s the original Java version, played from Logic Pro.
Class diagram: a Synthesiser holds a set of Voices, and each Voice has its own oscillator, filter and envelope.
Class diagram: Synthesiser contains many Voices; each Voice contains an oscillator, a resonant low-pass filter and an envelope; Sine, Saw, Square and Triangle oscillators extend Oscillator.

Keeping the audio loop clean

A synthesiser has to compute sound faster than you can listen to it. At 44,100 samples a second, in blocks of 256 samples, the engine has about 5.8 milliseconds to compute each block before the sound card runs dry and you hear a click. That is a real engineering constraint, and it breeds creativity in algorithm and system design: the kind of constraint I love working within. In Java, a garbage collection pause at the wrong moment is exactly that click.

The engine never creates objects while it plays. Every object is allocated once, at start-up, and reused throughout the life cycle of the program, the way an object pool works, so the garbage collector has nothing to clean up.

Expensive calculations are performed during start-up rather than at runtime. The waveforms and the filter coefficients are worked out once and stored in lookup tables, so the loop does lookups instead of calling Math.sin or Math.tan. Smaller things add up. The oscillator’s phase wraps with a bitwise AND instead of a modulo, and values like one over the sample rate are calculated once, so the loop multiplies where it would have divided.

What it showed me

This is the project that taught me how much a language’s memory model decides its performance: memory managed by hand in C++, garbage collected in Java, or tracked through lifetimes in Rust. Even with all that care, the JVM still underperforms a language with leaner memory management: under identical test bench conditions (8 voices, 2,000 audio blocks), the Rust port did the same work in about 45% less time. Careful code lowers the risk of a collector pause and doesn’t remove it, which is why C++ or Rust is the usual choice for something like this. As a way to learn what real-time code costs, it was worth it.

The version you’re playing

The demo above runs a Rust port of the engine, compiled to WebAssembly so it works in a browser. I made the port with agents rather than by hand (thanks, Claude!). It has the same structure as the Java version, and Rust has no garbage collector, so the problem I worked around in Java doesn’t arise. The interface is a copy of the Java one, so what you see is the original design and user experience.