Weird Constructor / Posts / HexoSynth Modular Synthesizer in Rust - Devlog #14: ?

HexoSynth Modular Synthesizer in Rust - Devlog #14: ?

The HexoSynth modular synthesizer (programmed in Rust) ?

A little jam made with the new ADSR envelope generator:

If you don't want to dig through the detailed log:

TLDR: Skip to the Devlog 14 Conclusion Section

Details

This is the more chatty part of the devlog. If this is too verbose for your interest, please skip down to the Devlog 14 Conclusion Section.

Work on the Adsr Envelope Generator Node

I've finished the testing of the Adsr node and added it officially to the changelogs and documentation of HexoDSP and HexoSynth. With this HexoSynth finally has a proper ADSR envelope. Eventually I will add an even more complicated envelope like an DAHDSHR envelope with multiple hold stages and an initial delay stage. But first I want to finish existing functionality more.

Addition of the FVaFilt Virtual Analog Filter

I've also continued fixing and finishing the FVaFilt (Virtual Analog Filter) node. Next up I want to write some tests and see where the limits of the current filters are. There are some quirks with the SVF filter as far as I know, if you drive it too rough and introduce too much DC offset.

Sadly the SVF filter type is a bit unstable around high drive values. But that is nothing I can change at this point. I still need to make the filter stereo though.

The contributor also provided even more work on the ladder filter and now provides multiple modes for it! It's amazing to have that filter available for high pass and band pass too!

I also took the time to improve comparing FFTs in the tests with a more visual representation for developing the tests:

FFT visualization in the HexoDSP test suite
FFT visualization in the HexoDSP test suite

After this, the work on the FVaFilt node is concluded. Here you find a little demo noodling around with some of it's parameters:

SynthConstructor API and the Rust1x1 Node

Some days ago I started designing a new API for HexoDSP, one that allows easy and fully type checked setup of a DSP graph in Rust. The changes introduced a new node into HexoDSP, which is primarily useful for other Rust applications than HexoSynth to build a DSP graph: The Rust1x1 node. It allows execution of native Rust code as a DSP node in a HexoDSP node graph.

In combination with the SynthConstructor API this allows easy setup of a graph. Here you find a snippet from the cpal_synth_constructor.rs example. It shows off the new hexodsp::build module API that provides functions and methods for every DSP node, parameter, setting and output port.

    // The SynthConstructor encapsulates the whole audio graph engine in HexoDSP:
    let mut sc = SynthConstructor::new();

    // Pass the NodeExecutor from SynthConstructor to the CPAL backend and start
    // the frontend thread:
    start_backend(sc.executor().unwrap(), move || {
        use hexodsp::build::*;

        // Setup a sawtooth oscillator with 440Hz:
        let saw = bosc(0).set().wtype(2).set().freq(440.0);
        // Setup an amplifier node with a low gain:
        let amp = amp(0).set().gain(0.2).input().inp(&saw.output().sig());

        // Insert your own custom Rust function via a NodeId::Rust1x1 node
        // into the DSP graph:
        use hexodsp::dsp::{DynamicNode1x1, DynNode1x1Context};
        let r1x1 = rust1x1(0).input().inp(&amp.output().sig());
        let r1x1 = r1x1.set().alpha(0.75);
        // You may replace this function anytime at runtime:
        sc.set_dynamic_node1x1(0, Box::new(|inp: &[f32], out: &mut [f32], ctx: &DynNode1x1Context| {
            let alpha = ctx.alpha_slice();
            for (i, in_sample) in inp.iter().enumerate() {
                out[i] = in_sample * alpha[i];
            }

            // This sets an atomic float that can be read out using SynthConstructor::led_value()!
            ctx.led_value().set(out[0]);
        }));

        // Assign amplifier node output to the two input channels
        // of the audio device output node:
        let out = out(0).input().ch1(&r1x1.output().sig());
        let out = out.input().ch2(&r1x1.output().sig());

        // Setup a triangle LFO with a cycletime of 8 seconds.
        let lfo = tslfo(0).set().time(8000.0);
        // Setup the "att"enuator input to 0.3 with a modulation amount of 0.0 to 0.7.
        // Redirect the output of the LFO (which oscillated between 0.0 and 1.0) to the
        // "att" input of the Amp node here:
        amp.set_mod().att(0.3, 0.7).input().att(&lfo.output().sig());

        // Upload the program:
        sc.upload(&out).unwrap();

        loop { /* ... */ }
    });

Another chunk of work is being put into the fixed MAX_ALLOCATED_NODES limit that is currently implicitly imposed. It's a limit that was setup at the beginning as the hexagonal matrix only allows for 8x8 (256) nodes.

I don't want to get rid of the limit for allowing more nodes, as more nodes are probably not going to run in realtime anyways. But for saving memory overhead if you just want to use HexoDSP for small things. Allocating a full 256 nodes DSP graph everytime does not make sense.

Also some other limits for HexoSynth will be lifted. Such as the maximum number of scopes, code blocks and feedbacks.

YouTube Sound Demos

Here is a little ADSR envelope generator demo:

And here a demo of the FVaFilt node:

Devlog 14 Conclusion

I've not released a devlog in a long time now. That is mostly down to some real life things I've had to deal with. Things have cleared up a bit more by now.

HexoSynth finally has an ADSR envelope generator now with the Adsr node. There are tons of other basic nodes like this that will trickle in over time. I've got a long TODO list to work on here.

The FVaFilt virtual analog filter model developed by Fredemus is also finished now and fully integrated into HexoDSP. I did not develop that filter model or understand the maths in it in depth. My focus for HexoSynth is on collecting lots of DSP code for users (like myself) to play around with and make music with. I put in some extra work here for a cohesive user experience. Such as documenting all paramters, making sure their value ranges map properly and are useable.

Last but not least I want to direct your eyes to the new SynthConstructor API in HexoDSP. It's an API directed towards Rust programmers that don't want to invent their own DSP graph and nodes, and just want to get started making some sounds. See above for more details about that API.

The TODO list now contains the following:

  • Improve patch saving/loading with a proper file save/open dialog.
  • Add more automated test cases for the UI workflow.

And a lot more things for the future are (not ordered by priority):

  • A clock signal generator (enter BPM, get out pulses!).
  • Noise generator with multiple colors of noises.
  • WBlockDSP visual programming language improvements and refinements.

Contact

In case you find this project interesting or have questions, you can reach me via Discord these days, check out the #hexosynth channel in the Rust Audio Discord. Optionally I'm also online on IRC (via Matrix) in the Linux Audio Developer channel #lad (nickname 'wct') on Libera.Chat: irc:#lad@irc.libera.chat.

Links

Weird Constructor Avatar
Weird Constructor

Nice end 30 y/o guy, geek and F(L)OSS developer that messes with: Linux, Windows, Networking, Interpreters/Compilers, Games, Audio, Music, GUIs, C/C++, Rust, Scheme/Lisp, 3D printing, electronics and more.


For updates follow me on Mastodon

All the F(L)OSS development on my projects happens in my spare time. If you find what I do useful, entertaining or just want to support me, you can do this via Liberapay:

Donate using Liberapay