Skip to content

Repository files navigation

Audioground

A drum machine and subtractive synthesizer built with React and Rust/WebAssembly.

Features

  • Real-time audio synthesis
  • Web MIDI input with synth/drum routing, sustain, saved routing settings, and hot-plug detection
  • Ten drum sounds, playable with the pads or A–; keys
  • Per-hit drum velocity through the engine API (linear amplitude from 0 to 1)
  • Subtractive synth with anti-aliased saw/pulse oscillators, pulse width, resonant low-pass filter, and ADSR
  • Bass, Lead, and Pluck presets with live sound controls
  • Pointer/touch keyboard and Q–I shortcuts (2, 3, 5, 6, 7 for sharps), octave and velocity controls
  • Mono mode with last-note priority, or Poly mode for up to eight simultaneous notes

Getting Started

Install Rust with the wasm32-unknown-unknown target and pnpm, then build the audio engine:

git clone https://github.com/PhysSong/audioground
cd audioground
pnpm i
pnpm build-wasm

Then, start the development server:

pnpm dev

Click Load audio to enable both instruments. Hold synth keys for sustained notes; release them to hear the envelope tail. Browser-held notes also release when the window loses focus, the page is hidden, the octave changes, or the preset menu opens. These actions release only browser input; other input groups retain their held notes. Sound controls and presets can be adjusted before loading audio.

Use Mono / Poly to switch between bass/lead playing and chords. Changing modes releases held notes; release tails finish naturally. In Poly mode, each note has its own oscillator, filter, and envelope. At the eight-voice limit, release tails are reused before the oldest held note. Voice mode stays selected when changing presets.

After changing Rust code, run pnpm build-wasm again to regenerate the local WASM bindings.

Drum hits accept an optional velocity: synth.trigger({ drum: "Snare", velocity: 0.5 }). The WASM API exposes engine.trigger(Drum.Snare, 0.5) (Rust: Some(0.5)). Omitting velocity defaults to full amplitude (1), preserving existing pad and keyboard hits. Finite values are clamped to 0–1; non-finite values default to 1. Zero skips the hit without cutting off existing tails. Velocity scales each voice independently after synthesis, preserving its timbre and decay.

The app shares a NoteController across inputs. Each input uses a group (such as browser or a MIDI device ID) and a source within it (such as a key code or channel/note pair). The first owner of a pitch supplies its attack and velocity; additional owners do not retrigger it, and only the last release sends Note Off. The keyboard highlights held pitches, which can differ from sounding voices in Mono mode or after voice stealing. Timed accessibility presses are canceled when their input group is released. Release all notes, voice-mode changes, and audio resets clear every input group. Click Enable MIDI to allow device access, then choose an Input device. The activity light responds to incoming messages from that input. Inputs appear and disappear automatically as devices connect; an unplugged selection waits for the same device to return. Disable MIDI closes the selected input; Cancel ignores a pending access request (the browser's permission prompt may still need dismissing). MIDI stays connected on window blur and while the page is hidden. This requires HTTPS or localhost and a browser with Web MIDI. Click Load audio to hear MIDI notes; messages received before audio is ready are not queued. The selected device is session-only; select it again after a reload.

Message routing settings are saved locally and can be reset with Reset routing:

  • Destination: Auto sends channel 10 to drums and the other channels to synth. Synth or Drums sends all accepted channels to that instrument.
  • Receive channel: accept all channels or just one (1–16).
  • Synth transpose: shift by up to two octaves in either direction. Pitches outside 0–127 are dropped; drums keep their assigned note numbers.
  • Velocity: use the controller's velocity or a fixed value (1–127), normalized to 0–1. Note On with zero velocity always means Note Off, even in fixed mode.
  • Drum note assignments: map each pad to a note number or Off. Multiple pads can share a note to layer sounds. Defaults are Kick 36, Snare 38, Closed Hat 42, Open Hat 46, Tom 45, Crash 49, Ride 51, Cowbell 56, Clap 39, and Shaker 70 (maracas).

Synth routing handles Note On/Off, sustain (CC 64), Reset All Controllers (CC 121), and All Notes Off (CC 123). Sustain is independent per device/channel; All Notes Off honors sustain, and Reset All Controllers releases pedal-held notes while retaining physically held keys. Repeated Note On for a held key is ignored; striking a pedal-held key again releases its old ownership and attacks again unless another input still owns that pitch. Drums trigger on every nonzero Note On and decay naturally; Note Off does not stop them. Other messages, including pitch bend, pressure, program changes, and CC 120, are currently inspector-only. See the MIDI message table and controller definitions.

Changing routing, switching/disconnecting/disabling the MIDI input, or resetting routing releases MIDI-owned synth notes and clears pedal state. UI-owned notes remain held. Release all notes, voice-mode changes, and audio resets clear both UI and MIDI ownership and pedal state. Releases use the synth envelope; existing drum tails finish naturally. MIDI continues playing on blur or a hidden page, just as its connection remains active.

Open Show event inspector to inspect incoming MIDI messages and UI note, drum, and sound-control events. It is hidden by default and captures only while open and unpaused. The latest 100 events stay in memory, newest first, with UTC timestamps, source labels, and MIDI bytes in hexadecimal. Use Pause / Resume to freeze capture and Clear to empty the history. Routed MIDI notes and drum hits are labeled MIDI; UI notes are labeled UI. Note events include shared-pitch ownership and automatic releases.

Use cargo test --manifest-path synth-engine/Cargo.toml, pnpm test, pnpm lint, and pnpm build to check the engine and app. pnpm test runs the Vitest suites once; pnpm test:watch reruns affected tests while editing. The app build also type-checks the TypeScript tests.

About

WASM WebAudio demo page

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages