Get started
From nothing to a plugin
Everything on this page comes from the repository’s README and its two example plugins, as they are at commit c5835bf.
Before you build on it: Manguebyte is young, it is not on crates.io yet, and the API will change before 1.0. Plugin editors exist and are tested in CI, but none has been opened in a host by hand yet.
Before you start
You need a stable Rust toolchain, version 1.85 or newer.
On Linux, the device backend needs the ALSA development headers:
sudo apt install libasound2-devAdd the dependency
Depend on the repository directly:
[dependencies] manguebyte = { git = "https://github.com/alessonqueirozdev-hub/manguebyte" }From README.md at commit c5835bf, as it is in the file.Make a sound
A tuple of processors is itself a processor. Build a synth voice by putting three of them in a tuple, then render one second of it to a WAV file. This needs no audio hardware.
use manguebyte::prelude::*; fn main() -> Result<(), wav::WavError> { let spec = ProcessSpec { sample_rate: 48_000.0, max_block_size: 48_000, num_channels: 2 }; // A tuple of processors is itself a processor. let mut voice = ( Oscillator::new(Waveform::Saw, note_to_hz(45.0)), Filter::new(FilterShape::Lowpass, 1_200.0, 1.5), Gain::from_db(-12.0), ); voice.prepare(&spec); let mut second = AudioBuffer::<f32>::new(2, 48_000); voice.process(&mut second); wav::write_file("saw.wav", &second, 48_000, WavFormat::Int16) }From README.md at commit c5835bf, as it is in the file.Play it live
The same kind of chain, on the default output device. The closure runs on the audio thread.
use manguebyte::prelude::*; fn main() -> Result<(), DeviceError> { let device = OutputDevice::open_default()?; let spec = ProcessSpec { sample_rate: device.sample_rate(), max_block_size: 512, num_channels: device.num_channels(), }; let mut voice = (Oscillator::new(Waveform::Sine, 440.0), Gain::<f32>::from_db(-18.0)); voice.prepare(&spec); // The closure runs on the audio thread. let _stream = device.start(512, move |buffer| voice.process(buffer))?; std::thread::sleep(std::time::Duration::from_secs(2)); Ok(()) }From README.md at commit c5835bf, as it is in the file.Run the examples
Clone the repository to run its examples and to build the example plugins:
git clone https://github.com/alessonqueirozdev-hub/manguebyte cd manguebyte # Render a short synth riff to a WAV file. Needs no audio hardware. cargo run --example render_wav -- riff.wav # Play the same riff on your speakers. The optional argument is a level in dB. cargo run --example play_riff -- -12Build a plugin
plugins/pluck is a complete instrument in one file: a 16-voice plucked synth with per-note expression and modulation, automation on the exact sample, a reverb and a small editor. plugins/duck is the same for an effect: a sidechain ducker with a bypass switch that does not click and an editor with a gain-reduction meter. Each becomes a CLAP, a VST 3 and an LV2 plugin with one line per format.
Build either one into something a host can load with the framework’s command-line tool,
cargo mangue, which writes a.clapand a.vst3in each system’s layout (add--format lv2for an.lv2bundle):cargo run -p cargo-mangue -- bundle -p mangue-pluck --releasecargo mangue new my-synth --kind instrumentstarts a plugin of your own from a template;installcopies a plugin into the user’s plugin folders, andvalidateruns the CLAP and VST 3 validators on it. Both examples go through those validators on every commit, and were tested by hand in REAPER as CLAP and VST 3, before they had editors.Declare parameters
Parameters are fields of a struct: typed, lock-free, with ranges, units and text conversion. This is the whole declaration from the example effect.
/// The plugin's parameters. pub struct DuckParams { /// Sidechain level, in decibels, at which the input starts to go down. pub threshold: FloatParam, /// The most the input is turned down, in decibels. pub depth: FloatParam, /// How fast the input goes down, in milliseconds. pub attack: FloatParam, /// How fast it comes back, in milliseconds. pub release: FloatParam, /// Lets the input through untouched. pub bypass: BoolParam, } param_set!(DuckParams { threshold, depth, attack, release, bypass });From plugins/duck/src/lib.rs at commit c5835bf, as it is in the file.Test it without a host
The same plugin runs inside a unit test through
Harness, on a machine with no sound card. This test from the example instrument plays an A and checks, by FFT, that 440 Hz is what comes out.#[test] fn plays_the_pitch_it_is_asked_for() { use manguebyte::dsp::fft::{Complex, RealFft}; let mut harness = Harness::<Pluck>::new(RATE, 512); harness.params().reverb.set(0.0); // No resonant peak, so the fundamental is the loudest partial. harness.params().resonance.set(0.7); harness.note(0, 40_000, 69, 1.0); // A4, 440 Hz let sound = harness.render(32_768); let mut fft = RealFft::<f32>::new(32_768); let mut spectrum = vec![Complex::ZERO; fft.spectrum_len()]; fft.forward(sound.channel(0), &mut spectrum); let loudest = (1..spectrum.len()) .max_by(|&a, &b| spectrum[a].norm_sqr().total_cmp(&spectrum[b].norm_sqr())) .unwrap(); let hertz = loudest as f64 * RATE / 32_768.0; assert!((hertz - 440.0).abs() < 2.0, "loudest partial at {hertz} Hz"); }From plugins/pluck/src/lib.rs at commit c5835bf, as it is in the file.Play it without a host
The standalone wrapper runs the instrument straight on the sound card, with any connected MIDI keyboard. The argument is the level in decibels.
cargo run -p mangue-pluck --features standalone --example play -- -12 holdWhere to read next
- ARCHITECTURE.md: how the crates fit together, and which thread runs what.
- The decision records: why parameters have no listeners, why there is one event stream, and the rest.
- Beyond JUCE: what the framework proves, and the limit of each claim.
- CONTRIBUTING.md: the rules for code that runs on the audio thread.