Manguebyte

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.

  1. Before you start

    You need a stable Rust toolchain, version 1.85 or newer.

    On Linux, the device backend needs the ALSA development headers:

    Shell
    sudo apt install libasound2-dev
  2. Add the dependency

    Depend on the repository directly:

    TOML
    [dependencies]
    manguebyte = { git = "https://github.com/alessonqueirozdev-hub/manguebyte" }
    From README.md at commit c5835bf, as it is in the file.
  3. 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.

    Rust
    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.
  4. Play it live

    The same kind of chain, on the default output device. The closure runs on the audio thread.

    Rust
    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.
  5. Run the examples

    Clone the repository to run its examples and to build the example plugins:

    Shell
    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 -- -12
  6. Build 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 .clap and a .vst3 in each system’s layout (add --format lv2 for an .lv2 bundle):

    Shell
    cargo run -p cargo-mangue -- bundle -p mangue-pluck --release

    cargo mangue new my-synth --kind instrument starts a plugin of your own from a template; install copies a plugin into the user’s plugin folders, and validate runs 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.

  7. 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.

    Rust
    /// 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.
  8. 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.

    Rust
    #[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.
  9. 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.

    Shell
    cargo run -p mangue-pluck --features standalone --example play -- -12 hold